SSM-Mybatis
中文文档:https://mybatis.org/mybatis-3/zh/getting-started.html
github:https://github.com/mybatis/mybatis-3
-
MyBatis 是一款优秀的持久层框架,
-
它支持自定义 SQL、存储过程以及高级映射。
-
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
-
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录
过程
-
开发流程
编写配置文件->编写mapper(可以使用注解)->编写xml(建立mapper映射)->配置文件扫描mapper->获取mapper代理或者直接定位xml方法名id->执行sql
-
执行流程
- Resources获取并加载全局配置文件
- 实例化SqlSessionFactoryBuilder构造器
- build(Stream)方法解析配置文件流XMLConfigBuilder到configuration对象
- 实例化SqlSessionFactory(携带configuration)
- 执行openSession并创建exector执行器以及Transaction事务管理器,返回DefaultSqlSession
Transaction tx = null; 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);
protected BaseExecutor(Configuration configuration, Transaction transaction) { this.transaction = transaction; // 事务 this.deferredLoads = new ConcurrentLinkedQueue<>(); this.localCache = new PerpetualCache("LocalCache");// 缓存 this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache"); this.closed = false; this.configuration = configuration; // 配置环境 this.wrapper = this; }
- getMapper获取Mapper代理,代理中会携带SqlSession对象,knownMappers会存放扫描到的Mapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); // 得到对应mapper的代理工厂 if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); // 获取mapper代理
-
实现CRUD
- 以selectList为例,会首先根据方法名statement找到对应方法的信息MappedStatement(包括sql,参数,返回值等),mappedStatements会存储所有xml中mapper配置扫描到的方法,然后executor.query执行查询
selectList(){} MappedStatement ms = configuration.getMappedStatement(statement); // 获取执行方法信息 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); // 执行器调用
- query方法中会解析传进来的MappedStatement,boundSql就是sql代码
@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
- 会首先查看缓存有没有,有就直接用,没有再查询调用执行器Executor的doQuery,并将查询的结果存入缓存
- StatementHandler会创建 Statement对象,resultHandler会处理结果
Cache cache = ms.getCache(); if (cache != null) { // 会首先查看缓存有没有 flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // 查询的结果存入缓存 } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 没有再查询
-
查看是否执行成功,回滚或提交
-
关闭openSession,断开连接或者返回连接到连接池
作用域
- SqlSessionFactoryBuilder用于解析xml创建SqlSessionFactory,一旦创建了 SqlSessionFactory,就不再需要它了,实例的最佳作用域是方法作用域(也就是局部方法变量)
- SqlSessionFactory应该是单例的,创建SqlSession使用
- SqlSession应该是线程级别的,类似于一次Http请求,每个SqlSession存储一个数据库连接池的connection
使用注解
- 一个方法如果使用了注解那就不能再在xml中配置sql标签(可以完全删除xml文件,但要在配置文件中扫描mapper),二者会产生冲突,但如果稍微复杂的语句,还是使用xml比较好,二者可以混合
- 可以使用@Param(name)控制多个参数名入参
- 即使配置了注解,也要写xml文件注册mapper,以便加载时可以根据namespace扫描到对应的mapper类从而找到注解
- 注解中使用script标签可以拼接动态sql
- 后期使用SpringBoot可以扫描到mapper文件夹,自动生成xml
执行sql的两种方法
1. sqlSession.select("方法名或者方法全路径",params) 直接操作xml
1. 通过Resources.getResourceAsStream(resource)解析xml文件,mapper
2. sqlSession.getMapper(Class<T> var1)方法是如何获取代理对象的?
通过类名和方法名可以唯一定位到一条xml的sql语句,然后处理参数和返回值即可
// 通过cglib我们可以只通过一个class而创建出他的代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserMapper.class);// 设置代理目标
System.out.println(UserMapper.class.getName()); // 获取类的完整路径,映射xml的命名空间namespace
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("执行了"+method.getName()); // 获取方法名,映射xml的id
System.out.println("参数:"+Arrays.toString(objects)); // 执行入参
// 业务逻辑 可以调用 select(method.getName(),objects)等方法
System.out.println( "返回值:"+method.getReturnType()); // 处理返回结果
return null;
}
});
UserMapper o = (UserMapper)enhancer.create();
System.out.println(o.getUserList());
// jdk动态代理
Class<UserMapper> clazz = UserMapper.class;
UserMapper mapper = (UserMapper)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
……
return null;
}
});
System.out.println(o.getUserList());
// 获得sqlSession对象,SqlSession继承了Closeable接口会自动关闭
try (SqlSession sqlSession = MybatisUtils.getSqlSession();){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.getUserList()); //方式1
System.out.println(sqlSession.selectList("getUserList"));//方式2
System.out.println(sqlSession.selectList("com.kuang.mapper.UserMapper.getUserList"));//方式3
}catch (Exception e){
e.printStackTrace();
}
注意事项
- 默认sqlsession是不开启事务的,增删改需要显示commit
- 当参数不确定或者名称不一致的时候可以使用Map传参,#{key}可以直接去除map对应key的值,等价于实体类的getKey(),如果返回没有合适的pojo模型的话也可以使用Map来接收(@keyMap()映射多条记录)
-- xml
<insert id="insert" parameterType="map">
insert into user values (#{userId},#{userName});
</insert>
-- java
HashMap<String, Object> map = new HashMap<>();
map.put("userId", 5);
map.put("userName", "橘右京");
System.out.println(mapper.insert2(map));
-
模糊查询:like concat("%",#{name},"%")
-
当返回参数和实体属性名不一致或者返回结果过于复杂(包含多个对象)的时候
- 可以属性as 别名
- resultMap标签结果集映射
- 使用Map接收结果(不推荐)
-
通过**<sql>标签配合<incude>**可以实现sql代码段的复用,可以指定参数,也可以使用引用环境的参数
<select id="selectById" resultMap="teacherMapper2">
SELECT * FROM teacher WHERE id = #{id}
<include refid="limit">
<property name="offset" value="3"/>
<property name="pageSize" value="5"/>
</include>
</select>
<sql id="limit">
<if test="id != null">
limit ${offset},${pageSize} // ${id}也可以直接使用
</if>
</sql>
-
关联查询
-
javaType用于返回java类型,ofType用于返回的集合泛型比如List<T>
-
assocation用于关联单个对象,通常有以下两种方法(一对一,多对一)
- 子查询:通过select关联一个接口查询
- 连接查询:直接处理查询出来的结果,通过result映射
<resultMap id="StudentMapper2" type="Student"> <id property="id" column="id"></id> <association property="teacher" javaType="Teacher"> <result property="id" column="tid"/> </association> </resultMap> //方法1 <resultMap id="StudentMapper" type="Student"> <id property="id" column="id"></id> <association property="teacher" column="tid" javaType="Teacher" select="com.kuang.mapper.TeacherMapper.selectById"/> </resultMap> //方法2
-
assocation用于关联集合对象,通常有以下两种方法同上(一堆多,多对多)
<resultMap id="teacherMapper2" type="Teacher"> <id property="id" column="tid"></id> <result property="name" column="tname"></result> <collection property="students" column="id" ofType="Student"> <result property="id" column="sid"></result> </collection> </resultMap> //方法1 <resultMap id="teacherMapper" type="Teacher"> <id property="id" column="id"></id> <collection property="students" column="id" select="com.kuang.mapper.StudentMapper.selectByTid"/> </resultMap> //方法2
-
缓存原理
Mybatis缓存分为两种,默认开启一级缓存
一级缓存是SqlSession级别,当在同一个SqlSession中执行两次相同的SQL语句时,会将第一次执行查询的数据存入一级缓存中,第二次查询时会从缓存中获取数据,而不用再去数据库查询,从而提高了查询性能,但如果SqlSession执行了insert,update,delete操作,并提交数据库,或者SqlSession结束后,这个SqlSession中的一级缓存就不存在了,sqlSession.clearCache()也可以情况sqlSession的缓存
二级缓存是mapper级别的缓存,多个SqlSession共用二级缓存(准确来说时同一个mapperProxyFactory对象),他们使用同一个Mapper的SQL语句操作数据库,获得的数据会存放在二级缓存中,通过下列方式配置(有时还需要实现序列化接口)
配置文件setting:<setting name="cacheEnabled" value="true"/> 具体mapper:<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> 精确到单个方法:<select id="selectById" flushCache="true" useCache="false">
- 在创建SqlSession的时候会创建并携带执行器Executor,newExecutor就会创建缓存了,同样executor.clearLocalCache()也能清空缓存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GeKMzeg6-1616324339463)(C:\Users\86173\AppData\Roaming\Typora\typora-user-images\image-20210321170038350.png)]
protected BaseExecutor(Configuration configuration, Transaction transaction) {
……
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
……
}
- 执行每一次查询,都会给这次查询生成一个对应的key,包括了方法的信息(ms)、参数、rowBoundsboundSql
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
- 获取方法的缓存集合 ,如果缓存不为空,查询缓存cache中时候有对应的key,有就直接返回
- 没有重新查询,并将查询出来的结果方法缓存中并返回
- 如果获取不到方法缓存(该方法没有被执行过),则执行执行器Executor的doQuery后localCache.putObject(key, list)添加缓存
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
- 但如果是新增,删除,修改操作,会清空缓存
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
<flushCacheIfRequired(ms)
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache); // getTransactionalCache(cache).clear();
}
/>
return delegate.update(ms, parameterObject);
}
- commit和close都会情况缓存
public void commit(boolean required) throws SQLException {
clearLocalCache();
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
} // commit
public void close(boolean forceRollback) {
try {
……
finally {
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
} // close
二级缓存
knownMappers会存储所有的mapperProxyFactory对象,mapperProxyFactory存储methodCache,所有就实现了二级缓存也就是Mapper级别的缓存,尽管代理对象不同,但每个代理对象的MapperProxy(实现了InvocationHandler)都指向同一个methodCache属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H0Fu5hhd-1616324339465)(C:\Users\86173\AppData\Roaming\Typora\typora-user-images\image-20210321172812291.png)]
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// mapperProxyFactory.newInstance(sqlSession);
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
<MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final Map<Method, MapperMethod> methodCache;/>
return newInstance(mapperProxy);
}