MyBatis获取数据库源
XML文件中(properties)加载顺序
- 如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
- • 首先读取在 properties 元素体内指定的属性。
- • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属 性指定的路径读取属性文件,并覆盖之前读 取过的同名属性。
- • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
- 因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件 次之,最低优先级的则是 properties 元素中指定的属性。
类型处理器(typeHandlers)
- MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值 时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
创建类型处理器
重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型
- 实现 org.apache.ibatis.type.TypeHandler 接口
- 继承一个类 org.apache.ibatis.type.BaseTypeHandler
覆盖已有的处理 Java String 类型的属性以及 VARCHAR 类型的参数和结果的类型处理器
```java
@MappedJdbcTypes(value=JdbcType.VARCHAR)
public class MyTypeHandler extends BaseTypeHandler{
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i,parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
String str=rs.getString(columnName);
return str+”test”;
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String str=rs.getString(columnIndex);
return str+”test”;
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String str=cs.getString(columnIndex);
return str+”test”;
}
}
//XML配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
### 指定JAVAType
- 在类型处理器的配置元素(typeHandler 元素)上增加一个 javaType 属性(比如: javaType="String");
- 在类型处理器的类上增加一个 @MappedTypes 注解指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解上的配置将被忽略。
### 指定JDBCType
- 在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType="VARCHAR")
- 在类型处理器的类上增加一个 @MappedJdbcTypes 注解指定与其关联的 JDBC 类型列 表。 如果在 jdbcType 属性中也同时指定,则注解上的配置将被忽略。
### ResultMap中的类型处理器
- 当在 ResultMap 中决定使用哪种类型处理器时,此时 Java 类型是已知的(从结果类型中获得),但是 JDBC 类型是未知的。 因此 Mybatis 使用 javaType=[Java 类 型], jdbcType=null 的组合来选择一个类型处理器。
- 除非显式地设置,否则类型处理器在 ResultMap 中将不会生效。 如果希望能在 ResultMap 中隐 式地使用类型处理器,那么设置 @MappedJdbcTypes 注解
- ```java
@MappedJdbcTypes(value=JdbcType.VARCHAR,includeNullJdbcType=true)
泛型类型处理器
创建能够处理多个类的泛型类型处理器。为了使用泛型类型处理器, 需要增加一 个接受该类的 class 作为参数的构造器,这样 MyBatis 会在构造一个类型处理器实例 的时候传入一个具体的类。
```java
public class MyTypeHandler2extends BaseTypeHandler {
private Classtype;
public MyTypeHandler2(Classtype){
if (type == null)
throw new IllegalArgumentException(“Type argument cannot be null”);
this.type=type;
}
}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
## ObjectFactory对象工厂
- 每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory) 实例来完成实例化工作。
- 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认 无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法
- 可以通过创建自己的对象工厂来覆盖对象工厂的默认行为。
- setProperties 方法可以被用来 配置 ObjectFactory,在初始化ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法。
- ```java
public class MyObjectFactory extends DefaultObjectFactory {
@Override
public void setProperties(Properties properties) {
Set<Object> objects = properties.keySet();
for(Object o:objects){
System.out.println(properties.get(o));
}
super.setProperties(properties);
}
@Override
public <T> T create(Class<T> type) {
return super.create(type);
}
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
return super.create(type,constructorArgTypes,constructorArgs);
}
@Override
public <T> boolean isCollection(Class<T> type) {
return Collection.class.isAssignableFrom(type);
}
}
//XML配置
<objectFactory type="com.testmybatis.objectfactory.MyObjectFactory">
<property name="test" value="success"/>
</objectFactory>
插件(plugins)// 待深入
- MyBatis 允许在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) //执行前
- ParameterHandler (getParameterObject, setParameters) //参数处理
- ResultSetHandler (handleResultSets, handleOutputParameters) //结果集处理
- StatementHandler (prepare, parameterize, batch, update, query)//SQL语句
使用
- 使用插件是非常简单的,只需实现 Interceptor 接口, 并指定想要拦截的方法签名
环境配置(environments)
基本配置
- MyBatis中可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种 环境。
- 如果连接多个数据库,就需要创建多个 SqlSessionFactory 实例
1 | <environments default="mysqldb"> |
关注点
默认使用的环境 ID(比如:default=”development”)。
每个 environment 元素定义的环境 ID(比如:id=”development”)。
事务管理器的配置(比如:type=”JDBC”)。
数据源的配置(比如:type=”POOLED”)。
事务管理器
MyBatis中有两种事务管理器
- JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接 来管理事务作用域。
- MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管 理事务的整个生命周期(比如 JEE 应用服务器的上下文)。
自定义 MyBatis 对事务的处理
首先实现 TransactionFactory 接口,用实现类的全限定名或类型别名代替JDBC或MANAGED。
- ```java
public interface TransactionFactory {
default void setProperties(Properties props) { // 从 3.5.2 开始,该方法为默
认方法
// 空实现
}
Transaction newTransaction(Connection conn);
Transaction newTransaction(DataSource dataSource, TransactionIsolationLev
el le1
2
3
4
5
6
7
8
9
10
11
12
- 在事务管理器实例化后,所有在 XML 中配置的属性将会被传递给 setProperties() 方 法。此时实现还需要创建一个 Transaction 接口的实现类。
- ```java
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
Integer getTimeout() throws SQLException;
}
- ```java
数据源(datasource)
- dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源,包含三种(type=”[UNPOOLED|POOLED|JNDI]”)
UNPOOLED:
这个数据源的实现会每次请求时打开和关闭连接,适用于对数据库连接可用性要求不高的简单应用程序。。UNPOOLED 类型的数据源仅仅需要配置以下 属性:
driver – 这是 JDBC 驱动的 Java 类全限定名
url – 这是数据库的 JDBC URL 地址。
username – 登录数据库的用户名。
password – 登录数据库的密码。
defaultTransactionIsolationLevel– 默认的连接事务隔离级别。
defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间。
POOLED:
- POOLED: 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创 建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
JNDI
- 为了能在如 EJB 或应用服务器这类容器中使用,容器可以集 中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只 需要两个属性:
- initial_context – 这个属性用来在 InitialContext 中寻找上下文
- data_source – 这是引用数据源实例位置的上下文路径。
数据库厂商标识(databaseIdProvider)
- databaseIdProvider元素主要是为了支持不同厂商的数据库。基于映射语 句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所 有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不 带 databaseId 的相同语句,则后者会被舍弃。
基本使用
- 配置
1 | <databaseIdProvider type="DB_VENDOR">//type为VendorDatabaseIdProvider别名 |
- 当SQL语句的databaseId与当前数据库不匹配时
1 | <select id="selectAll" resultType="Emp" databaseId="d2"> |
- 会产生下列错误:
- SQL语句的databaseId配置正确后,语句可以正常执行
1 | <select id="selectAll" resultType="Emp" databaseId="mysql"> |
自定义 DatabaseIdProvide
1 | public class MyDatabaseIdProvider implements DatabaseIdProvider { |
映射器(mappers)
mapper映射配置文件的查找顺序: 优先级为 package->resource->url->class 且resource/url/mapperClass三个值只能有一个值是有值的,
```java
//XMLConfigBuilder中的方法
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
//遍历解析mappers下的节点
for (XNode child : parent.getChildren()) {
//首先解析package节点
if (“package”.equals(child.getName())) {
//获取包名
String mapperPackage = child.getStringAttribute(“name”);
configuration.addMappers(mapperPackage);
} else {
//如果不存在package节点,那么扫描mapper节点
//resource/url/mapperClass三个值只能有一个值是有值的
String resource = child.getStringAttribute(“resource”);
String url = child.getStringAttribute(“url”);
String mapperClass = child.getStringAttribute(“class”);
//优先级 resource>url>mapperClass
if (resource != null && url == null && mapperClass == null) {
//如果mapper节点中的resource不为空
ErrorContext.instance().resource(resource);
//那么直接加载resource指向的XXXMapper.xml文件为字节流
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过XMLMapperBuilder解析XXXMapper.xml,可以看到这里构建的XMLMapperBuilde还传入了configuration,所以之后肯定是会将mapper封装到configuration对象中去的。
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//解析
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//如果url!=null,那么通过url解析
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
//如果mapperClass!=null,那么通过加载类构造Configuration
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
//如果都不满足 则直接抛异常 如果配置了两个或三个 直接抛异常
throw new BuilderException(“A mapper element may only specify a url, resource or class, but not more than one.”);
}
}
}
}
}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
## XML映射器
- SQL映射文件中的顶级元素:
- cache – 该命名空间的缓存配置
- cache-ref – 引用其它命名空间的缓存配置。
- resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
- •sql – 可被其它语句引用的可重用语句块。
- 以及基本的增删改查
### select语句配置
```xml
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
insert, update 和 delete语句配置
1 | <insert |
主键回填
通过设置useGeneratedKeys
- 通过设置useGeneratedKeys=”true”并把keyProperty 设置为目标属性
- 当目标属性不是第一列时,需设置KeyColumn
1 | <insert id="insertAuthor" useGeneratedKeys="true" |
批量插入时也可以传入一个 Author 数组或集合,并返回自动 生成的主键。
```xml
insert into Author (username, password, email, bio) values(#{item.username}, #{item.password}, #{item.email}, #{item.bio}) 1
2
3
4
5
6
7
8
9
10
### 通过selectKey标签
```xml
<insert id="insert" parameterType="Student">
<selectKey order="BEFORE" keyProperty="id" resultType="String">
select replace(UUID(),'-','')
</selectKey>
insert into student values(#{id},#{name},#{password})
</insert>
Mapper.xml中语句的参数
java基本类型和String,可以省略掉SQL语句标签中的parameterType
传入复杂对象时,应指定参数类型
对于复杂参数,可以自定义类型处理方式,指定一个特殊的类型处理器类(或别名)
- ```xml
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}1
2
3
4
5
- 对于数值类型,可以设置 numericScale 指定小数点后保留的位数
- ```xml
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
- ```xml
结果映射(resultmap)
- id & result:id 和 result 元素都将一个列的值映射到一个简单数据类型 (String, int, double, Date 等)的属性或字段.唯一不同是,id 元素对应的属性会被标记为对象的标识符,在比较对象实 例时使用。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。
- constructor - 用于在实例化类时,注入结果到构造方法中
- idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
- arg - 将被注入到构造方法的一个普通结果
- id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
- result – 注入到字段或 JavaBean 属性的普通结果
- association – 一个复杂类型的关联;许多结果将包装成这种类型 嵌套结果映射 – 关联可以指定为一个 resultMap 元素,或者引用一个
- collection – 一个复杂类型的集合嵌套结果映射 – 集合可以指定为一个 resultMap 元素,或者引用一个
- discriminator – 使用结果值来决定使用哪个 resultMap
- case – 基于某些值的结果映射.嵌套结果映射 – 一个 case 也是一个映射它本身的结果,因此可以包含很多相 同的元素,或者它可以参照一个外部的 resultMap
关联
MyBatis 有两种不同的方式 加载关联:
- 嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。(使用延迟加载可以避免”N+1查询问题”)
- 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。
当result中有重复使用的列时,可以用columnPrefix()为一个列起别名
- ```xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
对应的列为:
![image-20201013191722600](https://i.loli.net/2020/10/13/KurawigWVzxQY8v.png)
- N+1查询问题新的解决方法:
- 某些数据库允许存储过程返回多个结果集,或一次性执行多个语句,每个语句返回一个 结果集。 我们可以利用这个特性,在不使用连接的情况下,只访问数据库一次就能获得 相关数据。
**例:**存储过程中执行下面的查询并返回两个结果集:
```xml
SELECT * FROM BLOG WHERE ID = #{id}
SELECT * FROM AUTHOR WHERE ID = #{id}
- ```xml
在映射语句中,必须通过 resultSets 属性为每个结果集指定一个名字,多个名字使用 逗号隔开
1 | <select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" s |
自动映射
- MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名 字的属性(忽略大小写)
- 有三种自动映射等级:
- NONE - 禁用自动映射。仅对手动映射的属性进行映射。
- PARTIAL - 对除在内部定义了嵌套结果映射(也就是连接的属性)以外的属性进行映射 •
- FULL - 自动映射所有属性。
缓存
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用 全局的二级缓存,需要在你SQL 映射文件中添加一行
```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
- 该语句的效果如下:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓 存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者 修改,而不干扰其他调用者或线程所做的潜在修改。
- 当有调用其他映射文件时,@CacheNamespaceRef 注解指定缓存作用域,将结果缓存到指定作用域,但因此会与另外一个映射文件缓存作用域的缓存产生差别,使数据得到污染,不建议使用二级缓存。
- 清除策略:
- LRU – 最近最少使用:移除最长时间不被使用的对象。//默认
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
## 动态SQL
------
> MyBatis的映射文件中支持在基础SQL上添加一些逻辑操作,并动态拼接成完整的SQL之后再执行,以达到SQL复用、简化编程的效果。
### < sql >
```xml
<mapper namespace="com.qf.mybatis.part2.dynamic.BookDao">
<sql id="BOOKS_FIELD"> <!-- 定义SQL片段 -->
SELECT id,name,author,publish,sort
</sql>
<select id="selectBookByCondition" resultType="com.qf.mybatis.part2.dynamic.Book">
<include refid="BOOKS_FIELD" /> <!-- 通过ID引用SQL片段 -->
FROM t_books
</select>
</mapper>
< where >
1 | <select id="selectBookByCondition" resultType="com.qf.mybatis.part2.dynamic.Book"> |
< set >
1 | <update id="updateBookByCondition"> |
< trim >
< trim prefix=”” suffix=”” prefixOverrides=”” suffixOverrides=”” >代替< where > 、< set >
prefix 前缀 suffix后缀 prefixOverrides语句前被覆盖的 suffixOverrides语句后被覆盖的
1 | <select id="selectBookByCondition" resultType="com.qf.mybatis.day2.dynamic.Book"> SELECT id,name,author,publish,sort FROM t_books <trim prefix="WHERE" prefixOverrides="AND|OR"> <!-- 增加WHERE前缀,自动忽略前缀 --> <if test="id != null"> and id = #{id} </if> <if test="name != null"> and name = #{name} </if> <if test="author != null"> and author = #{author} </if> <if test="publish != null"> and publish = #{publish} </if> <if test="sort != null"> and sort = #{sort} </if> </trim></select> |
1 | <update id="updateBookByCondition"> UPDATE t_books <trim prefix="SET" suffixOverrides=","> <!-- 增加SET前缀,自动忽略后缀 --> <if test="name != null"> name = #{name} , </if> <if test="author != null"> author = #{author} , </if> <if test="publish != null"> publish = #{publish} , </if> <if test="sort != null"> sort = #{sort} </if> </trim> WHERE id = #{id}</update> |
< foreach >
1 | <delete id="deleteBookByIds"> DELETE FROM t_books WHERE id IN <foreach collection="list" open="(" separator="," close=")" item="id" index="i"> #{id} </foreach></delete> |
参数 | 描述 | 取值 |
---|---|---|
collection | 容器类型 | list、array、map |
open | 起始符 | ( |
close | 结束符 | ) |
separator | 分隔符 | , |
index | 下标号 | 从0开始,依次递增 |
item | 当前项 | 任意名称(循环中通过 #{任意名称} 表达式访问) |
< choose > < when > < otherwise >
类似 Java 中的 switch 语句。
1 | <select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG WHERE state = ‘ACTIVE’ <choose> <when test="title != null"> AND title like #{title} </when> <when test="author != null and author.name != null"> AND author_name like #{author.name} </when> <otherwise> AND featured = 1 </otherwise> </choose></select> |
script
要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。比如:
1
void updateAuthorValues(Author author);
bind
bind 元素允许在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文
1
<select id="selectBlogsLike" resultType="Blog"> <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" /> SELECT * FROM BLOG WHERE title LIKE #{pattern}</select>//bind将pattern调用方法获得title并在前拼接_来进行模糊查询
SQL语句的多数据库支持
- 如果配置了 databaseIdProvider,你就可以在动态代码中使用名为 “_databaseId” 的变 量来为不同的数据库构建特定的语句
1 | <insert id="insert"> <selectKey keyProperty="id" resultType="int" order="BEFORE"> <if test="_databaseId == 'oracle'"> select seq_users.nextval from dual </if> <if test="_databaseId == 'db2'"> select nextval for seq_users from sysibm.sysdummy1" </if> </selectKey> insert into users values (#{id}, #{name})</insert> |