EZLippi-浮生志

MyBatis教程

MyBatis是一个优秀的ORM(对象关系映射)框架,和Hibernate不太一样,Hibernate不用你编写一行SQL语句,而MyBatis需要手写SQL语句,优点就是能够灵活调试SQL语句。这篇文章主要对MyBatis的几个核心概念进行介绍,以及如何在项目中使用MyBatis。

MyBatis底层原理

MyBatis底层还是依赖JDBC来操作数据库,不过对JDBC的语法进行了封装,使用JDBC来操作数据库的流程一般是加载JDBC驱动->创建JDBC连接->创建Statement->执行查询->解析ResultSet->异常处理->关闭Statement和Connection,如果每次都这样做就比较繁琐。

MyBatis所有的数据库操作都是在SqlSession中操作,这样的话就可以缓存查询结果、不用重复创建JDBC连接。

MyBatis核心概念

SqlSessionFactoryBuilder

顾名思义就是采用了Builder模式,用于创建SqlSessionFactory对象。

SqlSessionFactory

SqlSessionFactory工厂方法,用于创建SqlSession。

SqlSession

SqlSession 的实例不是线程安全的,每个线程都应该有它自己的SqlSession 实例,应该随着HttpServletRequest的创建而创建,请求结束则销毁。

Mapper

承载了实际的业务逻辑,其生命周期比较短,由SqlSession创建,用于将Java对象和实际的SQL语句对应起来。

Spring环境下Mybatis初始化过程

在开发过程中通常结合Spring框架一起使用提高开发效率,由于Spring进行了控制反转,所以其中MyBatis的初始化过程和正常过程稍稍有些不同:

Spring框架会在classpath下找到MyBatis的核心配置文件,使用它来初始化一个SqlSessionFactory实例。mapper类也会作为bean注入到代码中去的,Spring会使用上一步创建的SqlSessionFactory实例来创建SqlSession的实例,然后再使用SqlSession尝试创建各个mapper对象。

于此同时,MyBatis会扫描classpath下的mapper映射XML文件(此路径可以自定义),对于每一个mapper接口,它的「类全名」会作为命名空间,来和映射文件中的mapper标签进行匹配。
对于每一个映射文件中的一个执行语句标签(如select、delete),MyBatis会把他们映射到SqlSession的方法上,创建mapper接口的一个实现类。
如果mapper接口和其映射文件一一匹配,则bean创建成功。

Mapper文件举例

下面的例子中首先创建了一个id为BaseResultMap的ResultMap,和Java模型数据的User相对应,并声明了数据库中列和模型的属性之间的对应关系,后面的Select语句可以引用这个ResultMap作为方法的返回值。然后创建了一个Base_Column_List的SQL语句,后面可以通过refid来引用这个SQL。具体的用法可以参考MyBatis文档

需要注意的是下面中的各个select、insert、update、delete的id需要和实际的com.ezlippi.mapper.UserMapper类中的方法一一对应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<mapper namespace="com.ezlippi.mapper.UserMapper" >

<!--定义了Select语句返回结果和实际POJO对象的属性映射关系-->
<resultMap id="BaseResultMap" type="com.ezlippi.model.User" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="userName" property="userName" jdbcType="VARCHAR" />
<result column="passWord" property="passWord" jdbcType="VARCHAR" />
<result column="user_sex" property="userSex" javaType="com.ezlippi.enums.UserSexEnum"/>
<result column="nick_name" property="nickName" jdbcType="VARCHAR" />
</resultMap>

<!-- 定义了一条SQL语句模板,后面可以通过<include refid="Base_Column_List"/>引用这条SQL -->
<sql id="Base_Column_List" >
id, userName, passWord, user_sex, nick_name
</sql>

<select id="getAll" resultMap="BaseResultMap" >
SELECT
<include refid="Base_Column_List" />
FROM users
</select>

<select id="getOne" parameterType="java.lang.Long" resultMap="BaseResultMap" >
SELECT
<include refid="Base_Column_List" />
FROM users
WHERE id = #{id}
</select>

<insert id="insert" parameterType="com.ezlippi.model.User" >
INSERT INTO
users
(userName,passWord,user_sex)
VALUES
(#{userName}, #{passWord}, #{userSex})
</insert>

<update id="update" parameterType="com.ezlippi.model.User" >
UPDATE
users
SET
<if test="userName != null">userName = #{userName},</if>
<if test="passWord != null">passWord = #{passWord},</if>
nick_name = #{nickName}
WHERE
id = #{id}
</update>

<delete id="delete" parameterType="java.lang.Long" >
DELETE FROM
users
WHERE
id =#{id}
</delete>
</mapper>

#{}和${}的区别

在拼接SQL语句中可以用”#{}”和”${}”来引用方法中的参数,两者的区别如下:

“#{}”在底层实现上使用?做占位符来生成PreparedStatement,然后将参数传入,大多数情况都应使用这个,它更快、更安全。

“${}”将传入的数据直接显示生成在sql中。如:order by ${user_id},如果传入的值是111,那么解析

Spring配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">

<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />

<!-- 配置dbcp数据源 引用jdbc.peoperties属性文件中的属性-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="initialPoolSize" value="${connection_pools.initial_pool_size}" />
<property name="minPoolSize" value="${connection_pools.min_pool_size}" />
<property name="maxPoolSize" value="${connection_pools.max_pool_size}" />
<property name="maxIdleTime" value="${connection_pools.max_idle_time}" />
<property name="acquireIncrement" value="${connection_pools.acquire_increment}" />
<property name="checkoutTimeout" value="${connection_pools.checkout_timeout}" />
</bean>

<!-- 配置mybatisSqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis/mybatis.xml"/>
<property name="mapperLocations" value="classpath*:com/ezlippi/**/*Mapper.xml"/>
</bean>

<!-- 配置SqlSessionTemplate -->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

<!-- mapper批量扫描,从mapper包中扫描出mapper接口,自动创建代理对象并且在spring容器中注册
遵循规范:将mapper.java和mapper.xml映射文件名称保持一致,且在一个目录 中
自动扫描出来的mapper的bean的id为mapper类名(首字母小写)
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.ezlippi.**.dao"/>
<property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>
</bean>

<!-- 事务配置 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<!-- 使用annotation注解方式配置事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>


<context:component-scan base-package="com.ezlippi" />
<context:annotation-config />

</beans>

其中如下代码为spring自扫描所有dao包并把其下的所有mybatis接口文件装配入容器。

1
2
3
4
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.ezlippi.**.dao"/>
<property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate"/>
</bean>

mybatis配置文件mybatis.xml如下,主要配置一些alias:

1
2
3
4
5
6
7
8
9
10
<configuration>
<typeAliases>
<typeAlias alias="Integer" type="java.lang.Integer" />
<typeAlias alias="Long" type="java.lang.Long" />
<typeAlias alias="HashMap" type="java.util.HashMap" />
<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
<typeAlias alias="ArrayList" type="java.util.ArrayList" />
<typeAlias alias="LinkedList" type="java.util.LinkedList" />
</typeAliases>
</configuration>

mybatis接口mapper需要加@Repository注解,方可直接在Service层直接自动装配注入。 mybatis接口mapper举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Repository
public interface UserMapper {

List<User> getAll();

User getOne(Long id);

void insert(User user);

void update(User user);

void delete(Long id);

}

Service层注入mybatis接口mapper的时候需要在构造方法中注入,这样注入mapper实例才不会空。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service(value = "userService")
public class UserServiceImpl {

private UserMapper mapper;

@Autowired
public UserServiceImpl(UserMapper userMapper) {
this.mapper = userMapper;
}

public List<User> selectAll() {
return userMapper.selectAll();
}
}

看到这里,可能会有一个疑问,上面我只声明了UserMapper接口,没有定义实现类怎么调用具体的方法呢,别担心,mybatis通过JDK的动态代理方式,在启动加载配置文件时,根据配置mapper的xml去生成Dao的实现。
session.getMapper()使用了代理,当调用一次此方法,都会产生一个代理class的instance。

最后就是UserControl类了,调用UserService来完成具体的业务逻辑,

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class UserControl {

@Autowired
private UserService userService;

@RequestMapping("/users/all")
public List<User> getAllUsers() {
return userService.selectAll();
}
}
🐶 您的支持将鼓励我继续创作 🐶