目录
官网:MyBatis中文网
入门示例
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.test.mapper.UserMapper">
<!--<select id="selectUsers" resultType="map">
select * from user;
</select>-->
</mapper>
UserMapper.java
package com.mybatis.test.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.Map;
@Mapper
public interface UserMapper {
@Select("select * from user")
Map<String, Object> selectUser();
}
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://10.211.55.7:3306/users?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"/>
<property name="username" value="root"/>
<property name="password" value="Gz2021.."/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
测试类:
package com.mybatis.test;
import com.mybatis.test.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
import java.util.List;
public class GetSqlSessionTest {
public static void main(String[] args) throws Exception {
String config = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(config);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过xml定义mapper
//List<Object> list = sqlSession.selectList("com.mybatis.test.mapper.UserMapper.selectUsers");
//System.out.println(list);
//通过接口定义mapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
System.out.println(userMapper.selectUser());
sqlSession.close();
}
}
SqlSession是非线程安全的,只能存在于当前请求作用于内,不能作为共享变量使用。
在最原始的Mybatis使用方式中,接口和mapper.xml的是sql的两种类型的容器,但使用接口封装sql时,还是需要通过namespace关联mapper.xml,然后导入mapper.xml,这样mybatis才能够识别接口中的方法。
全局配置
properties
<properties>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://10.211.55.7:3306/users?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"/>
<property name="username" value="root"/>
<properties/>
通过#{name}的方式在配置文件中使用。
settings
设置mybatis的一些内部行为。
<settings>
<!-- 打印mybatis日志 -->
<setting name="logImpl" value="STDOUT_LOGGING" />
<!-- 是否开启二级缓存 -->
<setting name="cacheEnabled" value="false"/>
<!-- 设置执行器类型 -->
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
typeAlias
设置别名,一边用在自定义类上,方便其他地方声明类型。mybatis对一内部的类型提供了默认的别名。
如果指定类型,别名就是给定的名称,如果指定包名,别名就是去掉包名的类名。
<typeAliases>
<package name="com.mybatis.test.domain"/>
</typeAliases>
//使用com.mybatis.test.domain包下的User时就不用使用全限定名了
<select id="selectUsers" resultType="User">
select * from user;
</select>
typeHandlers
类型处理器,当向sql中设置参数或从ResultSet获取结果集封成Bean时,会通过类型处理器进行类型转换。
mybatis提供了很多内置的类型处理器,如果有特殊需求,可以自定义类型处理器,然后通过该注解进行声明。
@MappedJdbcTypes(JdbcType.VARCHAR)
public class MyTypeHandler extends BaseTypeHandler<String> {
//设置参数
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
System.out.println("setNonNullParameter");
preparedStatement.setString(i, s);
}
//获取结果集
@Override
public String getNullableResult(ResultSet resultSet, String s) throws SQLException {
System.out.println("getNullableResult");
return resultSet.getString(s);
}
@Override
public String getNullableResult(ResultSet resultSet, int i) throws SQLException {
System.out.println("getNullableResult");
return resultSet.getString(i);
}
@Override
public String getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
System.out.println("getNullableResult");
return callableStatement.getString(i);
}
}
<typeHandlers>
<typeHandler handler="com.mybatis.test.typehandler.MyTypeHandler"/>
</typeHandlers>
objectFactory
对象工厂的作用是处理结果及时创建返回值的对象,如果想改变默认的行为,可以通过继承DefaultObjectFactory来实现。
plugins
插件的作用是对某些组件的执行过程进行拦截,主要针对以下四个组件:
- Executor
- ParameterHandler
- ResultSetHandler
- StatementHandler
自定义插件通过实现Interceptor以及@Interceptor注解来实现。
environments
environment → transactionManager(JDBC/Managed) → dataSource(POOLED、UNPOOLED、JNDI)
mappers
引入mapper.xml文件,有四种方式:
- resource
- url
- class
- package
mapper映射
cache、ref-cache、resultMap、sql、insert、update、delete、select
select
<select
#唯一标识符,用来引用这个sql,基于接口的使用方式中,这个id是方法名
id="selectPerson"
#参数类型
parameterType="int"
#已经废弃了
parameterMap="deprecated"
#结果类型,即用什么来封装数据库中的没一条记录
resultType="hashmap"
#结果类型映射,能够进行一些转换和一对多 多对一 多对多的配置,能够进行字段名和属性名的匹配配置
resultMap="personResultMap"
#是否刷新二级缓存
flushCache="false"
#是否使用二级缓存,默认为true,如果开启了二级缓存就会使用
useCache="true"
#驱动等待数据库返回数据的超时时间,是否支持 依赖于数据库的实现,很少使用
timeout="10"
#给数据库驱动一个建议,每次返回多少条,依赖于驱动的实现,很少使用
fetchSize="256"
#STATEMENT → Statement,PREPARED → PreparedStatement CALLABLE → CallableStatement
statementType="PREPARED"
#很少使用
resultSetType="FORWARD_ONLY">
update
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
#是否刷新二级缓存,默认为true
flushCache="true"
statementType="PREPARED"
#指定主键的属性名,通过与getGeneratedKeys或selectKey配合,给主键属性自动生成一个值
#selectKey是insert标签中的一个子标签,用于定义生成key值的规则
keyProperty=""
keyColumn=""
#生成key值,默认为false
useGeneratedKeys="false"
timeout="20">
<update
id="updateAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
<delete
id="deleteAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
sql
定义sql片段。
<sql id="">
...
<sql/>
<include refid="">
参数
#{prop} 与 ${prop}
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
结果集映射
结果集映射一般有以下几种形式:
- 通过resultType指定一个bean,属性名与sql返回的列名完全一直,mybatis自动映射。
- 通过resultType指定结果集类型为map,mybatis通过一个map来封装一条数据,key是列名。
- 通过resultMap指定一个自定义的映射,在复杂sql中比较常用,能够配置一对多、多对一等关系。
二级缓存
使用cache时,是为当前mapper创建一个单独的cache实例,使用cache-ref时,是与其他mapper共享一个缓存实例
<cache
#淘汰策略,LRU、FIFO、SOFT、WEAK
eviction="FIFO"
#刷新频率
flushInterval="60000"
#缓存对象的个数
size="512"
#是否为只读
readOnly="true"/>
日志
支持的日志框架类型:
- SLF4J
- Apache Commons Logging
- Log4j 2
- Log4j
- JDK logging
<setting name="logImpl" value="LOG4J"/>
#标准输出
<setting name="logImpl" value="STDOUT_LOGGING" />
可选的值有:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,或者是实现了 org.apache.ibatis.logging.Log 接口。
动态sql
if
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
choose/when/otherwise
相当于switch、case、defualt
<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>
where
通过where能够自动处理一些sql错误。
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
foreach
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
script
在基于接口的mapper中定义一些复杂的sql
@Update({"<script>",
"update Author",
" <set>",
" <if test='username != null'>username=#{username},</if>",
" <if test='password != null'>password=#{password},</if>",
" <if test='email != null'>email=#{email},</if>",
" <if test='bio != null'>bio=#{bio}</if>",
" </set>",
"where id=#{id}",
"</script>"})
void updateAuthorValues(Author author);
原理
SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Exeuctor、MappedStatement、StatementHandler、SqlSource、BoundSql、ParameterHandler、ResultHandler、TypeHandler。
关键点:数据源、一级缓存、二级缓存、日志、事务
mybatis流行的两点原因:
- ①对jdbc模板代码的封装
- ②提供了丰富的扩展功能
- ③提供了非常好的扩展性
创建SqlSessionFactory
//SqlSessionFactoryBuilder
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
//读取xml配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//创建SqlSessionFactory
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
创建SqlSession
mybatis-config.xml → SqlSessionFactoryBuilder → SqlSessionFactory → SqlSessionFactory.open
→ Configuration → Environment
→ DataSource → Transaction
→ Transaction → Executor → SqlSession
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
执行过程
Mapper.xml中的sql语句在解析过程中会被封装成MappedStatement,以Map的形式存放在Configuration中,statementId = namespace + sqlId。
→ SqlSession → 调用select/update/insert/delete → 传入statementId引用对应的sql语句要执行的sql对应的MappedStatement
→ 将MappedStatement交给Exeuctor去执行 → Executor内部提供了query/update等方法,SqlSession中的方法都是通过Executor支持的
→ 从MappedStatement中拿到BoundSql,即包含了参数的sql
→ 根据sql、参数、分页参数创建一个一级缓存(当前连接内的)的CacheKey
→ 根据CacheKey从本地缓存中获取数据,如果没有获取到再查询数据库
→ 根据各种参数创建一个StatementHandler,由StatementHandler全权负责sql语句的执行
→ 预处理:获取数据库连接(transaction.getConnection())、设置参数(ParameterHandler/TypeHandler)
→ 执行:通过PreparedStatement的execute方法向数据库发送请求,并获取结果
→ 处理结果集:ResultHandler/TypeHandler
→ 如果执行update,本地缓存会被清空,即使后续还有select操作,本地缓存也会被清空
//DefaultSqlSession.java
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
//BaseExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取BoundSql,包含了sql和参数
BoundSql boundSql = ms.getBoundSql(parameter);
//创建一级缓存的cache key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
//如果当前sql语句需要刷新缓存,就清空本地缓存
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//尝试从本地缓存中获取
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//如果本地缓存没有就尝试从数据库查找
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
//SimpleExecutor
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//创建StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//执行prepre,获取连接、设置参数
stmt = prepareStatement(handler, ms.getStatementLog());
//执行
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
缓存原理
mybatis缓存分为一级缓存和二级缓存,一级缓存的作用范围是SqlSession,二级缓存的作用范围是Application。
一级缓存
作用范围、存储结构、刷新时机、清空时机、CacheKey包含的内容。
一级缓存默认开启,包含了Executor内部。
public abstract class BaseExecutor implements Executor {
protected PerpetualCache localCache;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.localCache = new PerpetualCache("LocalCache");
}
}
PerPetualCache实际上就是一个HashMap,由于SqlSession本身就不是线程安全的,所以PerPetualCache也不需要考虑线程安全问题。
public class PerpetualCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap<>();
}
在执行查询时,如果本地缓存没有,就会从数据库查询,然后放入本地缓存,但当前sql上如果配置了flushCache=true,那么每次执行查询时都会先清除本地缓存。
- 配置了flushCache=true
- SqlSession被SqlSession.close
- 执行更新操作
- 执行SqlSession.clearCache()
CacheKey对象是缓存的key值,CacheKey中包含了:
- sql
- 分页参数
- 普通参数
- statementId
- environmentId
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
//statement Id
cacheKey.update(ms.getId());
//分页参数
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
//sql语句
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
//具体的查询参数
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
//environmentId
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
CacheKey的update方法是将该值作为计算CacheKey校验和的一部分。
public void update(Object object) {
//得到传入的对象的hashCode
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
//参与计算校验和的对象的个数
count++;
//累加计算CacheKey的校验和
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
二级缓存
开启条件:
<seting cacheEnable="true">
<cache/> <cache-ref namespace=""/> <cache class="XxxClass">
<select useCache="true"/>
<cache
#淘汰策略,LRU、FIFO、SOFT、WEAK
eviction="FIFO"
#刷新频率
flushInterval="60000"
#缓存对象的个数
size="512"
#是否为只读
readOnly="true"/>
原理和实现类:
二级缓存是应用级别的,mybatis内部实现了四个种类的缓存实现:LRU、FIFO、SOFT、WEAK,也可以基于第三方的缓存工具来作为二级缓存。
二级缓存的实现基础是被装饰的Executor → CachingExecutor 和 Cache接口
→ CachingExecutor 内部持有了一个Cache接口的实现,查询数据时先从这个Cache中找,如果找不到在交给BaseExecutor
→ Cache接口是mybatis的总的Cache接口,一级缓存和二级缓存都实现了该接口,对于二级缓存,有以下几种实现:
→ FifoCache FIFO淘汰策略
→ LruCache LRU淘汰策略
→ SoftCache 类似jvm的软引用淘汰策略,即当jvm内存不够用导致垃圾回收时,会被清除
→ WeakCache 类似jvm的若引用淘汰策略,只要发生jvm垃圾回收时,会被清除
→ BlockingCache 多个线程同时查询同一个key时,如果缓存中没有,则只能由一个线程去查询数据库,其余等待,类似于可以防止缓存穿透。
→ LogingCache 实现了在Debug级别下输出缓存命中日志
→ ScheduledCache 增加了基于刷新时间的缓存清除功能,flushInterval
→ SerializedCache 实现了对value的序列化
→ SynchronizedCache 通过在方法上增加synchronized关键字实现了同步功能
→ TransactionalCache 实现了事务功能,commit时加入缓存,rollback时从缓存中移除。
二级缓存最底层实际上还是PerpetualCache,只是在其基础上增加了上面的一系列的装饰类。
默认用的驱逐方式是LruCache,然后再根据配置,再次基础上增加一系列的装饰类。
//XMLMapperBuilder
private void cacheElement(XNode context) {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
public Cache build() {
setDefaultImplementations();
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
//设置标准的装饰器
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
//根据配置的属性来设置装饰器
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
//设置了定时清除
if (clearInterval != null) {
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
//设置成了读写缓存,需要对操作进行同步
if (readWrite) {
cache = new SerializedCache(cache);
}
//增加日志输出装饰
cache = new LoggingCache(cache);
//增加序列化装饰
cache = new SynchronizedCache(cache);
//设置blocking=true,增加BlockingCache,此时多个线程对同一个key进行查询时,只会有一个
//线程区查询数据库,其余的会进行等待
if (blocking) {
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
实例:
//启动5个线程执行同一个sql
for (int i = 0;i < 5;i ++) {
new Thread(new Runnable() {
@Override
public void run() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
System.out.println(userMapper.selectUser(1));
System.out.println(users);
sqlSession.close();
}
}).start();
}
<mapper namespace="com.mybatis.test.mapper.UserMapper2">
<cache blocking="true"/>
<select id="selectUsers" resultType="User" parameterType="int">
select * from user where id = #{id};
</select>
</mapper>
//从结果中看,只进行了一次数据库查询,如果不设置blocking=true,会进行5次数据库查询,
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6d8ae5f8]
==> Preparing: select * from user where id = ?
==> Parameters: 1(Integer)
==> Parameters: 1(Integer)
==> Parameters: 1(Integer)
==> Parameters: 1(Integer)
==> Parameters: 1(Integer)
<== Columns: id, name, age, sex
<== Columns: id, name, age, sex
<== Columns: id, name, age, sex
<== Columns: id, name, age, sex
<== Columns: id, name, age, sex
<== Row: 1, zhangsan, 10, male
<== Row: 1, zhangsan, 10, male
<== Row: 1, zhangsan, 10, male
<== Row: 1, zhangsan, 10, male
<== Row: 1, zhangsan, 10, male
getNullableResult
getNullableResult
getNullableResult
getNullableResult
getNullableResult
<== Total: 1
<== Total: 1
<== Total: 1
<== Total: 1
<== Total: 1
{sex=male, name=zhangsan, id=1, age=10}
{sex=male, name=zhangsan, id=1, age=10}
{sex=male, name=zhangsan, id=1, age=10}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2e00b060]
{sex=male, name=zhangsan, id=1, age=10}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4993cff4]
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@19f1af7b]
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6d8ae5f8]
{sex=male, name=zhangsan, id=1, age=10}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@21be90cd]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2e00b060]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@19f1af7b]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4993cff4]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6d8ae5f8]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@21be90cd]
没有配置bocking=true时的结果,执行了5次查询
Created connection 1028948052.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3d548054]
Created connection 734471668.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2bc725f4]
==> Preparing: select * from user where id = ?
==> Preparing: select * from user where id = ?
Created connection 1520525687.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5aa16177]
==> Preparing: select * from user where id = ?
Created connection 1857298393.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6eb41fd9]
==> Preparing: select * from user where id = ?
Created connection 910917581.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@364b7fcd]
==> Preparing: select * from user where id = ?
==> Parameters: 1(Integer)
==> Parameters: 1(Integer)
==> Parameters: 1(Integer)
==> Parameters: 1(Integer)
==> Parameters: 1(Integer)
<== Columns: id, name, age, sex
<== Columns: id, name, age, sex
<== Columns: id, name, age, sex
<== Columns: id, name, age, sex
<== Columns: id, name, age, sex
<== Row: 1, zhangsan, 10, male
<== Row: 1, zhangsan, 10, male
<== Row: 1, zhangsan, 10, male
<== Row: 1, zhangsan, 10, male
<== Row: 1, zhangsan, 10, male
getNullableResult
getNullableResult
getNullableResult
getNullableResult
getNullableResult
<== Total: 1
<== Total: 1
<== Total: 1
<== Total: 1
<== Total: 1
{sex=male, name=zhangsan, id=1, age=10}
{sex=male, name=zhangsan, id=1, age=10}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5aa16177]
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2bc725f4]
{sex=male, name=zhangsan, id=1, age=10}
{sex=male, name=zhangsan, id=1, age=10}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6eb41fd9]
{sex=male, name=zhangsan, id=1, age=10}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@364b7fcd]
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3d548054]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3d548054]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2bc725f4]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6eb41fd9]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@364b7fcd]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5aa16177]
数据源
mybatis的数据源在原生模式下通过environment配置,有三种选择
- POOLED 池化连接池
- UNPOOLED 非池化连接池,类似jdbc
- JNDI 基于jndi的连接池
DataSourceFactory
→ JndiDataSourceFactory
→ PooledDataSourceFactory
→ PooledDataSource
→ UnpooledDataSourceFactory
→ UnpooledDataSource
但一般在使用mybatis的时候,会把连接池和事务管理托管给spring,这里就涉及到spring和mybatis的集成包 mybatis-spring:2.0.6,在这个包中提供了一个SqlSessionFactoryBean,内部包含了Mybatis的组件,spring通过注册SqlSessionFactoryBean集成mybatis。
public SqlSessionFactoryBean writeSqlSessionFactoryBean() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
sqlSessionFactoryBean.setMapperLocations(new FileSystemResource("/Users/dongzhang/develop/workspace/echo20222022/open_src/spring_cloud_demo/cloud-user/src/main/resources/mappers/UserMapper.xml"));
return sqlSessionFactoryBean;
}
SqlSessionFactoryBean实现了InitializingBean,当spring容器启动时会调用afterPropertiesSet方法,该方法在spring体系下读取配置,然后构造mybatis的Configuration,用这个Configuration对象创建一个SqlSessionFactory。
同时,这个包还提供了@MapperScanner,在spring启动的时候将指定包下的类的BeanDefination注册到了Spring中。
//SqlSessionFactoryBean
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
if (StringUtils.hasText(defaultScope)) {
scanner.setDefaultScope(defaultScope);
}
scanner.registerFilters();
//扫描指定的包,向spring中注册对应接口的BeanDefinition
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
另外,在springboot环境下使用mybatis时,有一个mybatis-spring-boot-starter,引入了MybatisAutoConfiguration自动装配类,在这个类中注册了SqlSessionFactoryBean,会自动注入spring环境中的DataSource,然后SqlSessionFactoryBean同时还会自动扫描Mapper并注册到spring中。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
return factory.getObject();
}
mybatis的解析environment标签的逻辑:
//XMLConfigBuilder
//解析environment标签
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//解析数据源类型,并根据类型返回一个工厂对象
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
break;
}
}
}
}
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
日志
mybatis为了适配不同的日志框架,在内部引入了一个统一的Log接口和适配各种日志框架的适配器(适配器设计模式),可以通过setting进行配置
<setting name="logImpl" value="SLF4J" />
可选择值:SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
统一接口:Log、LogFactory
具体实现:Slf4jLoggerImpl、StdOutImpl、BaseJdbcLogger、JakartaCommonsLoggingImpl等。
事务
mybatis的事务通过enverment配置,有两种类型,JDBC和MANAGED,JDBC类型时通过jdbc的Connection来管理事务,MANAGED类型不会做任何事情,一般用在和其他框架集成。
public class ManagedTransaction implements Transaction {
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
//提交和回滚没有做任何事情
@Override
public void commit() throws SQLException {
// Does nothing
}
@Override
public void rollback() throws SQLException {
// Does nothing
}
}
public class JdbcTransaction implements Transaction {
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
//通过connection进行提交
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + connection + "]");
}
connection.commit();
}
}
//通过connection进行回滚
@Override
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + connection + "]");
}
connection.rollback();
}
}
protected void openConnection() throws SQLException {
this.connection = this.dataSource.getConnection();
if (this.level != null) {
this.connection.setTransactionIsolation(this.level.getLevel());
}
this.setDesiredAutoCommit(this.autoCommit);
}
}
当跟Spring进行集成时,创建SqlSessionFactoryBean时创建的是:
public class SpringManagedTransactionFactory implements TransactionFactory {
@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
@Override
public Transaction newTransaction(Connection conn) {
throw new UnsupportedOperationException("New Spring transactions require a DataSource");
}
@Override
public void setProperties(Properties props) {
// not needed in this version
}
}
public class SpringManagedTransaction implements Transaction {
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
@Override
public void commit() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
LOGGER.debug(() -> "Committing JDBC Connection [" + this.connection + "]");
this.connection.commit();
}
}
@Override
public void rollback() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
LOGGER.debug(() -> "Rolling back JDBC Connection [" + this.connection + "]");
this.connection.rollback();
}
}
private void openConnection() throws SQLException {
//从线程池中获取连接
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
+ (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
}
}
设计模式
- 创建者模式
- 工厂方法模式
- 装饰模式
- 适配器模式