MyBatis(一)-基础
参考文献
JDBC操作
-
说明 JDBC 操作的核心步骤
- 注册数据库驱动类,指定数据库地址,其中包括 DB 的用户名、密码及其他连接信息;
- 调用
DriverManager.getConnection()
方法创建Connection
连接到数据库; - 调用
Connection
的createStatement()
或prepareStatement()
方法,创建Statement
对象,此时会指定 SQL(或是 SQL 语句模板 + SQL 参数);
-
通过
tatement
对象执行 SQL 语句,得到 ResultSet 对象,也就是查询结果集;- 遍历
ResultSet
,从结果集中读取数据,并将每一行数据库记录转换成一个 JavaBean 对象;
- 遍历
-
关闭
ResultSet
结果集、Statement
对象及数据库Connection
,从而释放这些对象占用的底层资源.
MyBatis特性
- MyBatis中一个重要的功能就是可以帮助Java开发封装重复性的JDBC代码.
- 通过Mapper映射配置文件以及相关注解,将Result结果映射为Java对象,在具体的映射规则中可以嵌套其他映射规则和必要的子查询.
- Mybatis相对于Hibernate和各类JPA实现框架更加灵活,更加轻量级,更加可控.
- 可以在Mybatis的Mapper映射文件中,直接编写原生的SQL语句,应用底层数据库产品的方言,则可以直接优化SQL语句.
- 还可以按照数据库的使用规则,让原生SQL语句选择我们期望的索引,从而保证服务的性能.
- MyBatis提供了强大的动态SQL功能来帮助开发者摆脱重复劳动.只需要在映射配置文件中编写动态SQL语句,MyBatis就可以根据执行时传入的实际参数拼凑出完整的,可执行的SQL语句.
Hibernate,Spring Data JPA,MyBaits对比
- 从性能角度,Hibernate,Spring Data JPA在对SQL语句的掌控,SQL手工调试,多表连接查询等方面,不及MyBatis直接使用原生SQL语句方便,高效.
- 从可移植角度,Hibernate屏蔽了底层数据库方言,Spring Data JPA屏蔽了ORM的差异,而MyBatis因为直接编写原生SQL,会与具体的数据完全绑定
- 从开发效率角度,Hibernate,Spring Data JPA处理中小型项目的效率会略高于MyBatis.
MyBatis使用步骤
-
定义操作接口以及XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public interface CustomerMapper {
// 根据用户Id查询Customer(不查询Address)
Customer find(long id);
// 根据用户Id查询Customer(同时查询Address)
Customer findWithAddress(long id);
// 根据orderId查询Customer
Customer findByOrderId(long orderId);
// 持久化Customer对象
int save(Customer customer);
} -
无需真正实现
CustomerMapper
接口,而是在resources/mapper
目录下配置相应的配置文件–CustomerMapper.xml
,在该文件中需要执行的SQL语句以及查询结果集的映射规则.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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80<mapper namespace="org.example.dao.CustomerMapper">
<!-- 定义映射规则 -->
<resultMap id="customerSimpleMap" type="Customer">
<!-- 主键映射 -->
<id property="id" column="id"/>
<!-- 属性映射 -->
<result property="name" column="name"/>
<result property="phone" column="phone"/>
</resultMap>
<!-- 定义映射规则 -->
<resultMap id="customerMap" type="Customer">
<!-- 主键映射 -->
<id property="id" column="id"/>
<!-- 属性映射 -->
<result property="name" column="name"/>
<result property="phone" column="phone"/>
<!-- 映射addresses集合,<collection>标签用于映射集合类的属性,实现一对多的关联关系 -->
<collection property="addresses" javaType="list" ofType="Address">
<id property="id" column="address_id"/>
<result property="street" column="street"/>
<result property="city" column="city"/>
<result property="country" column="country"/>
</collection>
</resultMap>
<!-- 定义t_order_item与OrderItem对象之间的映射关系-->
<resultMap id="orderItemtMap" type="OrderItem">
<id property="id" column="id"/>
<result property="amount" column="amount"/>
<result property="orderId" column="order_id"/>
<!--映射OrderItem关联的Product对象,<association>标签用于实现一对一的关联关系-->
<association property="product" javaType="Product">
<id property="id" column="product_id"/>
<result property="name" column="name"/>
<result property="description" column="description"/>
<result property="price" column="price"/>
</association>
</resultMap>
<!-- 定义select语句,CustomerMapper接口中的find()方法会执行该SQL,
查询结果通过customerSimpleMap这个映射生成Customer对象-->
<select id="find" resultMap="customerSimpleMap">
SELECT * FROM t_customer WHERE id = #{id:INTEGER}
</select>
<!-- 定义select语句,CustomerMapper接口中的findWithAddress()方法会执行该SQL,
查询结果通过customerMap这个映射生成Customer对象-->
<select id="findWithAddress" resultMap="customerMap">
SELECT c.*,a.id as address_id, a.* FROM t_customer as c join t_address as a
on c.id = a.customer_id
WHERE c.id = #{id:INTEGER}
</select>
<!-- CustomerMapper接口中的findByOrderId()方法会执行该SQL,
查询结果通过customerSimpleMap这个映射生成Customer对象-->
<select id="findByOrderId" resultMap="customerSimpleMap">
SELECT * FROM t_customer as c join t_order as t
on c.id = t.customer_id
WHERE t.customer_id = #{id:INTEGER}
</select>
<!-- 定义select语句,OrderMapper接口中的findByCustomerId()方法会执行该SQL,
查询结果通过orderMap这个映射生成Order对象.注意这里大于号、小于号在XML中的写法-->
<select id="findByCustomerId" resultMap="orderMap">
SELECT * FROM t_order WHERE customer_id = #{id}
and create_date_time <![CDATA[ >= ]]> #{startTime}
and create_date_time <![CDATA[ <= ]]> #{endTime}
</select>
<!-- 定义insert语句,CustomerMapper接口中的save()方法会执行该SQL,
数据库生成的自增id会自动填充到传入的Customer对象的id字段中-->
<insert id="save" keyProperty="id" useGeneratedKeys="true">
insert into t_customer (id, name, phone)
values (#{id},#{name},#{phone})
</insert>
</mapper> -
Java操作
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
39public class DaoUtils {
private static SqlSessionFactory factory;
static { // 在静态代码块中直接读取MyBatis的mybatis-config.xml配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
System.err.println("read mybatis-config.xml fail");
e.printStackTrace();
System.exit(1);
}
// 加载完mybatis-config.xml配置文件之后,会根据其中的配置信息创建SqlSessionFactory对象
factory = new SqlSessionFactoryBuilder()
.build(inputStream);
}
public static <R> R execute(Function<SqlSession, R> function) {
// 创建SqlSession
SqlSession session = factory.openSession();
try {
R apply = function.apply(session);
// 提交事务
session.commit();
return apply;
} catch (Throwable t) {
// 出现异常的时候,回滚事务
session.rollback();
System.out.println("execute error");
throw t;
} finally {
// 关闭SqlSession
session.close();
}
}
} -
MyBatis配置文件
mybatis-config.xml
,配置要连接的数据库地址,Mapper.xml等信息.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
56
57
58
59
60
61
62
63<configuration>
<properties> <!-- 定义属性值 -->
<property name="username" value="root"/>
<property name="id" value="xxx"/>
</properties>
<settings><!-- 全局配置信息 -->
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<!-- 配置别名信息,在映射配置文件中可以直接使用Customer这个别名
代替org.example.domain.Customer这个类 -->
<typeAlias type="org.example.domain.Customer" alias="Customer"/>
<typeAlias type="org.example.domain.Address" alias="Address"/>
<typeAlias type="org.example.domain.Order" alias="Order"/>
<typeAlias type="org.example.domain.OrderItem" alias="OrderItem"/>
<typeAlias type="org.example.domain.Product" alias="Product"/>
</typeAliases>
<environments default="development">
<environment id="development">
<!-- 配置事务管理器的类型 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源的类型,以及数据库连接的相关信息 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="xxx"/>
</dataSource>
</environment>
</environments>
<!-- 配置映射配置文件的位置 -->
<mappers>
<mapper resource="mapper/CustomerMapper.xml"/>
<mapper resource="mapper/AddressMapper.xml"/>
<mapper resource="mapper/OrderItemMapper.xml"/>
<mapper resource="mapper/OrderMapper.xml"/>
<mapper resource="mapper/ProductMapper.xml"/>
</mappers>
</configuration>
* Service层操作
```java
public class ProductService {
// 创建商品
public long createProduct(Product product) {
// 检查product中的各个字段是否合法
Preconditions.checkArgument(product != null, "product is null");
Preconditions.checkArgument(!Strings.isNullOrEmpty(product.getName()), "product name is empty");
Preconditions.checkArgument(!Strings.isNullOrEmpty(product.getDescription()), "description name is empty");
Preconditions.checkArgument(product.getPrice().compareTo(new BigDecimal(0)) > 0,
"price<=0 error");
return DaoUtils.execute(sqlSession -> {
// 通过ProductMapper中的save()方法完成持久化
ProductMapper productMapper = sqlSession.getMapper(ProductMapper.class);
return productMapper.save(product);
});
}
}
MyBatis架构
-
MyBatis分为三层架构,分别为基础支撑层,核心处理层和接口层.
-
基础支撑层
-
类型转换模块
-
<typeAliase>
别名机制 -
实现MyBatis中JDBC类型与Java类型之间相互转换.
1
2JDBC类 <-- SQL绑定实参 <-- 类型转换 <-- SQL绑定实参 <-- Java类型
JDBC类 --> 结果集映射 --> 类型转换 --> 结果集映射 --> Java类型
-
-
日志模块
-
反射工具模块
-
Binding模块
-
数据源模块
-
缓存模块
-
解析器模块
-
事务管理模块
-
-
核心处理层
-
配置解析
- MyBatis有三处可以添加配置信息的地方,分别是
mybatis-config.xml
配置文件Mapper.xml
配置文件Mapper
接口中的注解细信息.
- 在MyBatis初始化过程中,会加载这些配置信息,并解析之后得到的配置对象保存到
Configuration
对象中.
- MyBatis有三处可以添加配置信息的地方,分别是
-
SQL解析与
scripting
模块- MyBatis的动态SQL功能,只需要通过MyBatis提供的标签即可根据实际的运行条件动态生成实际的SQL语句.
<where>
,<if>
,<set>
标签等. - MyBatis中
scripting
模块就是负责动态生成SQL的核心模块
- MyBatis的动态SQL功能,只需要通过MyBatis提供的标签即可根据实际的运行条件动态生成实际的SQL语句.
-
SQL执行
-
在MyBatis中,要执行一条SQL语句,会涉及非常多的组件,比较核心的有
Executor
,StatementHandler
,ParameterHandler
和ResultSetHandler
-
其中,
Executor
会调用事务管理模块实现事务的相关控制,同时会通过缓存模块管理一级缓存和二级缓存.SQL语句的真正执行将会由StatementHandler
实现.StatementHandler
会依赖ParameterHandler
进行SQL模板的实参绑定,然后由java.sql.Statement
对象将SQL语句以及绑定好的实参传到数据库执行,从数据库中拿到ResultSet
,最后,由ResultSetHandler
将Result
映射成Java对象返回给调用方.
-
-
插件
-
-
接口层
- 接口层是MyBatis暴露给调用的接口集合
- 这些接口都是使用 MyBatis 时最常用的一些接口,例如,SqlSession 接口、SqlSessionFactory 接口等.其中,最核心的是 SqlSession 接口,你可以通过它实现很多功能,例如,获取 Mapper 代理、执行 SQL 语句、控制事务开关等
MyBatis配置
1 | properties 属性 |
Mybatis中的#
和$
的区别
-
#{}
是预编译处理.MyBatis在处理#{}
时,会将sql中的#{}
替换为?
调用PrepareStatement
的set方法,#
将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号.- 如:
where username=#{username}
,如果传入的值是111,那么解析成sql时的值为where username="111"
, 如果传入的值是id,则解析成的sql为where username="id"
.
- 如:
-
${}是字符串替换,${}将传入的数据直接显示生成在sql中.
- 如:
where username=${username}
,如果传入的值是111,那么解析成sql时的值为where username=111
;
如果传入的值是;drop table user;
,则解析成的sql为:select id, username, password, role from user where username=;drop table user;
- 如:
-
#方式能够很大程度防止sql注入,$方式无法防止Sql注入.
-
$方式一般用于传入数据库对象,例如传入表名.
-
一般能用#的就别用$,若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止sql注入攻击.
-
在MyBatis中,“${xxx}”这样格式的参数会直接参与SQL编译,从而不能避免注入攻击.但涉及到动态表名和列名时,只能使用“${xxx}”这样的参数格式.所以,这样的参数需要我们在代码中手工进行处理来防止注入.
-
结论:在编写MyBatis的映射语句时,尽量采用“#{xxx}”这样的格式.若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止SQL注入攻击.
一级缓存和二级缓存
- 一级缓存的生命周期默认与 SqlSession 相同
- 二级缓存则与应用程序的生命周期相同,与二级缓存相关的配置主要有下面三项:
- 二级缓存全局开关.这个全局开关是 mybatis-config.xml 配置文件中的 cacheEnabled 配置项.当 cacheEnabled 被设置为 true 时,才会开启二级缓存功能,开启二级缓存功能之后,下面两项的配置才会控制二级缓存的行为
- 命名空间级别开关.在 Mapper 配置文件中,可以通过配置 <cache> 标签或 <cache-ref> 标签开启二级缓存功能
- 在解析到 <cache> 标签时,MyBatis 会为当前 Mapper.xml 文件对应的命名空间创建一个关联的 Cache 对象(默认为 PerpetualCache 类型的对象),作为其二级缓存的实现.此外,<cache> 标签中还提供了一个 type 属性,我们可以通过该属性使用自定义的 Cache 类型.
- 在解析到 <cache-ref> 标签时,MyBatis 并不会创建新的 Cache 对象,而是根据 <cache-ref> 标签的 namespace 属性查找指定命名空间对应的 Cache 对象,然后让当前命名空间与指定命名空间共享同一个 Cache 对象.
- 语句级别开关.可以通过 <select> 标签中的 useCache 属性,控制该 select 语句查询到的结果对象是否保存到二级缓存中,useCache 属性默认值为 true