Series 35, Spring + SpringMVC + MyBatis integration

1. Overview

Integrate Spring, SpringMVC, MyBatis.

2. Integration steps

2.1, pom

<dependencies>
<!-- Use Sl4j annotations in ordinary maven projects -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.10</version>
</dependency>

<!-- Tools -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.22</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>2.12.1</version>
</dependency>

<!-- Integration of three major frameworks -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.4</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.4</version>
</dependency>

<!-- Dynamic proxy -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
</dependency>

</dependencies>

<build>
<!--
\t\tillustrate:
In maven, by default maven requires us to place the xxxMapper.xml configuration file, properties configuration, etc. in the resources directory. If we force it to be placed in the java directory,
By default, this configuration file will be automatically ignored when packaging.

Scenario 1: Put the xxxMapper.xml file and the xxxMapper interface in the same package
Solution: Add the following configuration in the resources directory

Scenario 2: Place the xxxMapper.xml file in the resources directory
Solution: Create the same Directory as xxxMapper in the resources directory
Pitfall: Note that Directory is created, not the package.
-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>

2.2, db.properties

db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/20231106_ssm?useSSL=false &useUnicode=true &characterEncoding=UTF8 &serverTimezone=GMT
db.username=root
db.password=123456

2.3, init.sql

drop DATABASE if EXISTS 20231106_ssm;
create DATABASE 20231106_ssm;
use 20231106_ssm;

CREATE TABLE `user` (
                         `id` int NOT NULL AUTO_INCREMENT COMMENT 'primary key',
                         `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'username',
                         `password` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'password',
                         `age` int NULL DEFAULT NULL COMMENT 'age',
                         PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

INSERT INTO `user` VALUES (1, 'zhangsan', 'zhangsan123', 23);
INSERT INTO `user` VALUES (2, 'lisi', 'lisi123', 24);

2.4, applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/ context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx .xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:property-placeholder location="classpath:db.properties"/>

    <context:component-scan base-package="org.star" use-default-filters="true">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
        <property name="driverClassName" value="${db.driver}"/>
        <property name="url" value="${db.url}"/>
        <property name="username" value="${db.username}"/>
        <property name="password" value="${db.password}"/>
    </bean>

    <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="typeAliasesPackage" value="org.star.entity.model"/>
        <property name="configuration">
            <bean class="org.apache.ibatis.session.Configuration">
                <property name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"></property>
                <property name="cacheEnabled" value="true"></property>
            </bean>
        </property>
        <property name="mapperLocations" value="classpath:mappers/*.xml"/>
        <!--
        <property name="mapperLocations">
            <list>
                <value>classpath*:org/star/mapper/*.xml</value>
            </list>
        </property>
        -->
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" id="mapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
        <property name="basePackage" value="org.star.mapper"/>
    </bean>

    <!-- Transaction Manager -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--
        Declarative transactions: Transactions in spring are implemented based on aop, but use interceptors instead of notifications
    -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--
            Define transaction object information
        -->
        <tx:attributes>
            <!--
                disable*: represents all methods starting with disable
            -->
            <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* org.star.service.UserService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"></aop:advisor>
    </aop:config>

</beans>

2.5, dispatcherServlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/ context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc .xsd">

    <context:component-scan base-package="org.star" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <mvc:annotation-driven/>

</beans>

2.6, web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- spring configuration information -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- springmvc configuration information -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:dispatcherServlet.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- Global garbled filter configuration -->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceRequestEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

2.7, UserDO

/**
 * @Author: A leaf of duckweed returns to the sea
 * @Date: 2023/11/6 11:34
 * @Description:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@ToString(callSuper = true)
public class UserDO implements Serializable {
    /**
     * serial number
     */
    private Integer id;

    /**
     * username
     */
    private String username;

    /**
     * password
     */
    private String password;

    /**
     * age
     */
    private Integer age;
}

2.8, UserMappper

/**
 * @Author: A leaf of duckweed returns to the sea
 * @Date: 2023/11/6 11:35
 * @Description:
 */
public interface UserMapper {

    /**
     * Add user
     * @param param
     */
    void saveUser(UserDO param);

    /**
     * Query all users
     * @return
     */
    List<UserDO> listAllUser();

    /**
     * Query users based on id
     * @param id
     * @return
     */
    UserDO getUserById(Integer id);
}

2.9, UserMapper.xml

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.star.mapper.UserMapper">

    <insert id="saveUser">
        insert into `user`(username,password,age) values (#{username},#{password},#{age})
    </insert>


    <select id="listAllUser" resultType="userDO">
        select * from `user`
    </select>

    <select id="getUserById" resultType="userDO">
        select * from `user` where id = #{id}
    </select>
</mapper>

2.10, UserService

/**
 * @Author: A leaf of duckweed returns to the sea
 * @Date: 2023/11/6 11:36
 * @Description:
 */
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public List<UserDO> listAllUser() {

        return userMapper.listAllUser();
    }

    public void saveUser(UserDO param) {
        userMapper.saveUser(param);
    }

    /**
     * Without this annotation, multiple database queries will be executed.
     * Reference: https://blog.51cto.com/u_15942107/6019692
     * @param id
     * @return
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public UserDO getUserById(Integer id) {
        UserDO u1 = userMapper.getUserById(id);
        UserDO u2 = userMapper.getUserById(id);
        UserDO u3 = userMapper.getUserById(id);
        System.out.println(u1);
        System.out.println(u2);
        System.out.println(u3);

        return userMapper.getUserById(id);
    }
}

2.11, UserController

@Slf4j
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/saveUser")
    public String saveUser(@RequestBody UserDO param) {
        log.info("UserController saveUser param:{}", param);
        userService.saveUser(param);
        return "SUCCESS";
    }

    @GetMapping("/listAllUser")
    public List<UserDO> listAllUser() {
        log.info("=================>listAllUser");
        return userService.listAllUser();
    }

    @GetMapping("/getUserById/{id}")
    public UserDO getUserById(@PathVariable("id") Integer id) {
        log.info("=================>getUserById id:{}",id);
        return userService.getUserById(id);
    }

}

3. Test

http://localhost:8080/listAllUser

4. Project structure