本文共 27440 字,大约阅读时间需要 91 分钟。
接上一篇
本篇讲解Mybatis传统XML配置开发、注解开发、缓存使用和插件使用
在父级项目下新创建一个模块mybatis-learn-dev
,可参考我的仓库:
之前的demo开发已经简单讲解了Mybatis基于XML配置的增删改查功能,本篇继续讲解其他高级功能的使用。
以博文和作者为例子,一篇博文对应一个作者。查询一篇博文同时查询出作者的信息。
public class Author { private int id; private String username; private String password; private String email; // 省略get和set方法}
public class Blog { private int id; private int authorId; private String title; // 省略get和set方法}
public interface BlogMapper { ListfindAll();}
@org.junit.Test public void test6() throws Exception { //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //2.解析了配置文件,并创建了sqlSessionFactory工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //3.生产sqlSession SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交 BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); Listblogs = blogMapper.findAll(); sqlSession.close(); System.out.println(blogs); }
resultMap标签也可以定下成下边这种形式:
以博文和作者为例子,一个作者可以有多篇博文。查询所有作者同时查询出每个作者所写的博文。
public class Author { private int id; private String username; private String password; private String email; private ListblogList;}
public interface AuthorMapper { ListfindAll();}
@org.junit.Test public void test2() throws Exception { //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //2.解析了配置文件,并创建了sqlSessionFactory工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //3.生产sqlSession SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交 AuthorMapper authorMapper = sqlSession.getMapper(AuthorMapper.class); Listauthors = authorMapper.findAll(); sqlSession.close(); System.out.println(authors); }
我们以博文和标签作为例子,一个博文可以配置多个标签,一个标签可以被多个博文使用。(提前建好标签表和博文标签关联关系表)
public class Blog { private int id; private int authorId; private String title; private Author author; private ListtagList;}
public class Tag { private int id; private String tagName; private String tagType;}
public interface BlogMapper { ListfindAll(); List findAllBlogAndTag();}
@org.junit.Test public void test3() throws Exception { //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //2.解析了配置文件,并创建了sqlSessionFactory工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //3.生产sqlSession SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交 BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); Listblogs = blogMapper.findAllBlogAndTag(); sqlSession.close(); for (Blog blog : blogs) { System.out.println(blog); } }
举例一些Mybatis的常用注解,
@Insert——新增 @Update——更新 @Delete——删除 @Select——查询 @Result——结果集,替代了<result>标签, @Results——多个结果集,替代的是<resultMap>,使用格式:@Results(@Result())或者@Results({@Result(),@Result()}) @One——一对一的结果集 @Many——一对多的结果集重新以用户表作为例子,实现注解的形式完成增删改查
public class User2 { private int id; private String name; private String password;}
public interface UserMapper2 { @Insert("insert into user values(#{id},#{name},#{password})") int insertUser(User2 user); @Update("update user set name = #{name} where id = #{id}") int updateUser(User2 user); @Delete("delete from user where id = #{id}") int deleteUser(User2 user); @Select("select * from user") ListqueryAllUser();}
引入mapper接口所在的类或者引入所在的包,两种都可以。
@org.junit.Test public void test4() throws Exception { //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //2.解析了配置文件,并创建了sqlSessionFactory工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //3.生产sqlSession SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交 UserMapper2 userMapper2 = sqlSession.getMapper(UserMapper2.class); User2 user2 = new User2(); user2.setId(2); user2.setName("lisi"); user2.setPassword("123456"); int insertUser = userMapper2.insertUser(user2); sqlSession.commit(); System.out.println("******Test insert user:" + insertUser); User2 user3 = new User2(); user3.setId(2); user3.setName("lisi2"); user3.setPassword("123456"); userMapper2.updateUser(user3); sqlSession.commit(); System.out.println("******Test update user:" + user3.toString()); Listlist = userMapper2.queryAllUser(); System.out.println("******Test query user:" + list); User2 user4 = new User2(); user4.setId(2); int deleteUser = userMapper2.deleteUser(user4); sqlSession.commit(); System.out.println("******Test delete user:" + deleteUser); sqlSession.close(); }
仍旧以博文和作者为例,一篇博文属于一位作者
新建一个包dao2存放注解开发的mapper 接口
public interface AuthorMapper2 { @Select("select * from author where id = #{id}") @Results({ @Result(id = true,property = "id",column = "id"), @Result(property = "username",column = "user_name"), @Result(property = "password",column = "password"), @Result(property = "email",column = "email") }) Author queryAuthorById(int id);}
public interface BlogMapper2 { @Select("select * from blog") @Results({ @Result(id=true,property = "id",column = "id"), @Result(property = "authorId",column = "author_id"), @Result(property = "title",column = "title"), @Result(property = "author",column = "author_id", javaType = Author.class, one = @One(select = "com.learn.dev.dao2.AuthorMapper2.queryAuthorById")) }) ListqueryAll();}
@org.junit.Test public void test5() throws Exception { //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //2.解析了配置文件,并创建了sqlSessionFactory工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //3.生产sqlSession SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交 BlogMapper2 blogMapper2 = sqlSession.getMapper(BlogMapper2.class); Listblogs = blogMapper2.queryAll(); sqlSession.close(); for (Blog blog : blogs) { System.out.println(blog); } }
仍旧以博文和作者为例,一个作者有多篇博文,而一篇博文只属于一位作者
@Select("select * from blog where author_id = #{authorId}") @Results({ @Result(id=true,property = "id",column = "id"), @Result(property = "authorId",column = "author_id"), @Result(property = "title",column = "title") }) ListqueryAllBlogByAuthorId(int authorId);
@Select("select * from author") @Results({ @Result(id = true, property = "id", column = "id"), @Result(property = "username", column = "user_name"), @Result(property = "password", column = "password"), @Result(property = "email", column = "email"), @Result(property = "blogList", column = "id", javaType = List.class, many = @Many(select = "com.learn.dev.dao2.BlogMapper2.queryAllBlogByAuthorId")) }) ListqueryAllAuthorAndBlog();
public void test6() throws Exception { //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //2.解析了配置文件,并创建了sqlSessionFactory工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //3.生产sqlSession SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交 AuthorMapper2 authorMapper2 = sqlSession.getMapper(AuthorMapper2.class); Listauthors = authorMapper2.queryAllAuthorAndBlog(); sqlSession.close(); for (Author author : authors) { System.out.println(author); } }
仍旧以博文和标签作为例子,一篇博文有多个标签,一个标签也可以配置在多个博文上
public interface TagMapper { @Select("SELECT t.id,t.tag_name,t.tag_type FROM blog_tag a INNER JOIN tag t ON a.tag_id = t.id where a.blog_id = " + "#{id}") @Results({ @Result(id = true,property = "id",column = "id"), @Result(property = "tagName",column = "tag_name"), @Result(property = "tagType",column = "tag_type") }) ListqueryTagByBlogId(int id);}
@Select("select * from blog") @Results({ @Result(id=true,property = "id",column = "id"), @Result(property = "authorId",column = "author_id"), @Result(property = "title",column = "title"), @Result(property = "tagList",column = "id", javaType = List.class, many = @Many(select = "com.learn.dev.dao2.TagMapper.queryTagByBlogId")) }) ListqueryAllBlogAndTag();
@org.junit.Test public void test7() throws Exception { //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //2.解析了配置文件,并创建了sqlSessionFactory工厂 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //3.生产sqlSession SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交 BlogMapper2 blogMapper2 = sqlSession.getMapper(BlogMapper2.class); Listblogs = blogMapper2.queryAllBlogAndTag(); sqlSession.close(); for (Blog blog : blogs) { System.out.println(blog); for (Tag tag : blog.getTagList()) { System.out.println("*****"+tag); } } }
缓存实际上就是存在内存里的数据库执行结果的数据,使用缓存可以提高我们的应用响应效率。 Mybatis提供了一级缓存和二级缓存两种机制。
新建一个名叫mybatis-learn-cache
的子工程。
新建User类;
public class User { private int id; private String name; private String password;}
新建UserMapper接口
public interface UserMapper { User queryUserById(int id);}
新建UserMapper.xml
新增log4j.properties配置文件
Mybatis一级缓存又叫SqlSession缓存,在操作数据库时候会创建一个SqlSession对象,对象中有一个HashMap的数据结构来存储缓存数据。不同SqlSession之间的HashMap互不影响。
public interface UserMapper { User queryUserById(int id);}
public class Test { private SqlSessionFactory sqlSessionFactory; @Before public void before() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); } @org.junit.Test public void Test1() { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.queryUserById(1); System.out.println(user); User user2 = userMapper.queryUserById(1); System.out.println(user2); sqlSession.close(); }}
public interface UserMapper { User queryUserById(int id); int updateUser(User user);}
update user u set u.name = #{name} where u.id = #{id}
@org.junit.Test public void Test2() { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.queryUserById(1); System.out.println(user); user.setName("ceshi"); userMapper.updateUser(user); sqlSession.commit(); User user2 = userMapper.queryUserById(1); System.out.println(user2); sqlSession.close(); }
SqlSession
执行commit()
方法的操作,则会清空SqlSession中的缓存。一级缓存离不开SqlSession
,所以我们从SqlSession
类开始寻找缓存是如何创建的。在SqlSession
类中只找到一个叫clearCache();
的方法,一步步跟进发现
PerpetualCache
发现,缓存其实是private final Map<Object, Object> cache = new HashMap<>();
一个HashMap的数据结构。 我们很容易猜想到缓存的创建是在Executor
类,我们看一下它的query
方法,它的具体实现是在BaseExecutor
类
@Override publicList query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
其中createCacheKey
就是创建缓存的key。继续向下看,
@Override publicList 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()) { clearLocalCache(); } List list; try { queryStack++; list = resultHandler == null ? (List ) 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; }
如果在list在localCache.getObject(key)
查不到的话,就调用queryFromDatabase
方法去数据库查询,我们继续看这个方法
privateList queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List 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; }
在这个方法里,执行完数据库查询操作后,会进行localCache
的写入putObject
private final Map
所以最终是Map的put,缓存对象存在这个map中。
同样,我们也可以看看BaseExecutor
的update
和commit
方法
@Override public void commit(boolean required) throws SQLException { if (closed) { throw new ExecutorException("Cannot commit, transaction is already closed"); } clearLocalCache(); flushStatements(); if (required) { transaction.commit(); } } @Override public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } clearLocalCache(); return doUpdate(ms, parameter); }
这俩个方法都会执行clearLocalCache();
方法。
@Override public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } }
@Override public void clear() { cache.clear(); }
所以sqlSession涉及到更新、删除、提交的时候会清除一级缓存。
二级缓存和一级缓存原理一样,不同的是它基于mapper的namespace,也意味着多个sqlSession可以共享同一个mapper的二级缓存。
与一级缓存的默认开启不同,Mybatis的二级缓存需要我们手动开启。
PerpetualCache
类是Mybatis默认实现缓存的类,当然我们也可以实现Cache
接口来自定义缓存。所以二级缓存底层其实还是一个Map数据结构。public class User implements Serializable { private static final long serialVersionUID = 8164800911245107262L; private int id; private String name; private String password;}
@org.junit.Test public void Test3() { SqlSession sqlSession1 = sqlSessionFactory.openSession(); UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class); User user1 = userMapper1.queryUserById(1); System.out.println(user1); sqlSession1.close(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class); User user2 = userMapper2.queryUserById(1); System.out.println(user2); sqlSession2.close(); }
测试结果:
@org.junit.Test public void Test4() { SqlSession sqlSession1 = sqlSessionFactory.openSession(); UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class); User user1 = userMapper1.queryUserById(1); System.out.println(user1); sqlSession1.close(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class); User user2 = new User(); user2.setId(1); user2.setName("李四"); user2.setPassword("123"); userMapper2.updateUser(user2); sqlSession2.commit(); sqlSession2.close(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class); User user3 = userMapper3.queryUserById(1); System.out.println(user3); sqlSession3.close(); }
测试结果:
在mapper.xml配置文件中还可以配置userCache和flushCache。
Mybatis在四大组件(Executor̵、StatementHandler̵、ParameterHandler̵、ResultSetHandler)提供了 简单易用的插件扩展机制。支持利用插件对四大核心对象进行拦截,增强核心对象的功能。本质上是借助于底层动态代理实现的。
Mybatis的四大对象(Executor̵、StatementHandler̵、ParameterHandler̵、ResultSetHandler)在创建完成后都执行了interceptorChain.pluginAll()
方法
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
获取所有的拦截器,调用interceptor.plugin(target)
并返回target
对象。我们可以使用AOP的方式创建出代理对象,拦截四大对象的每个操作。
新建一个插件类
@Intercepts({ @Signature( type = Executor.class, method = "query", args = { MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class} )})public class MyInterceptor implements Interceptor { }
修改sqlMapConfig.xml配置文件
这样,Mybatis在启动的时候加载插件类并保存在InterceptorChain҅
中。我们在执行某条SQL语句时,先创建SqlSession
,同时创建了Executor
实例,Mybatis会为该实例创建代理对象,这样我们插件的逻辑会在Executor
类相关方法调用前执行。
@Intercepts({ @Signature( type = Executor.class,//要拦截的接口 method = "query",//要拦截的接口内的方法 args = { MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class}//拦截方法的入参 )})public class MyInterceptor implements Interceptor { //每次方法的拦截都会进入 @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("*********进入方法增强************"); //继续执行原方法 return invocation.proceed(); } @Override public Object plugin(Object target) { //target要包装的对象,为该拦截器生成代理对象并放入拦截器链中 return Plugin.wrap(target,this); } @Override public void setProperties(Properties properties) { //插件初始化的时候调用,设置在XML文件里配置的属性 System.out.println(properties); }}
@org.junit.Test public void test5() { SqlSession sqlSession1 = sqlSessionFactory.openSession(); UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class); User user1 = userMapper1.queryUserById(1); System.out.println(user1); sqlSession1.close(); }
测试结果:
public class Plugin implements InvocationHandler { private final Object target; private final Interceptor interceptor; private final Map, Set > signatureMap; private Plugin(Object target, Interceptor interceptor, Map , Set > signatureMap) { this.target = target; this.interceptor = interceptor; this.signatureMap = signatureMap; } public static Object wrap(Object target, Interceptor interceptor) { Map , Set > signatureMap = getSignatureMap(interceptor); Class type = target.getClass(); Class [] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set methods = signatureMap.get(method.getDeclaringClass()); if (methods != null && methods.contains(method)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } private static Map , Set > getSignatureMap(Interceptor interceptor) { Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); // issue #251 if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } Signature[] sigs = interceptsAnnotation.value(); Map , Set > signatureMap = new HashMap<>(); for (Signature sig : sigs) { Set methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>()); try { Method method = sig.type().getMethod(sig.method(), sig.args()); methods.add(method); } catch (NoSuchMethodException e) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); } } return signatureMap; } private static Class [] getAllInterfaces(Class type, Map , Set > signatureMap) { Set > interfaces = new HashSet<>(); while (type != null) { for (Class c : type.getInterfaces()) { if (signatureMap.containsKey(c)) { interfaces.add(c); } } type = type.getSuperclass(); } return interfaces.toArray(new Class [interfaces.size()]); }}
分析:
从这块源码中我们可以看到Plugin
实现了InvocationHandler
接口,所有它的invoke(Object proxy, Method method, Object[] args)
方法会拦截所有方法的调用,在invoke(Object proxy, Method method, Object[] args)
方法里,对所拦截的方法进行检测,判断是否执行插件的逻辑。 Set<Method> methods = signatureMap.get(method.getDeclaringClass())
获取所有被拦截的方法列表;if (methods != null && methods.contains(method)) {}
判断方法列表是否包含被拦截的方法,如果包括则执行插件的逻辑interceptor.intercept(new Invocation(target, method, args))
;method.invoke(target, args);
Invocation
类存放的是目标类、方法、参数。
参考资料:
分页助手PageHelper是将分页的复杂操作进行封装。下边演示如何使用:
com.github.pagehelper pagehelper 5.1.11
@org.junit.Test public void test6() { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); PageHelper.startPage(0,2); Listusers = userMapper.queryAllUser(); for (User user : users) { System.out.println(user); } sqlSession.close(); }
测试结果:
通用Mapper插件是为了解决单表的增删改查,基于Mybatis的插件机制,不需要写SQL,不需要在dao层增加方法,只需要提供实体类即可。
tk.mybatis mapper 4.1.5
由于从3.2.0版本开始,该插件移除了MapperInterceptor
类,不再支持在mybatis核心配置文件中配置,需要依赖spring或者spring boot 环境。所以此处先不深入学习了,后边我们独立讲解一下。
未完。。。待续~~
本篇我们讲解了Mybatis基于XML的开发模式、基于注解的开发模式、Mybatis缓存、Mybatis插件的使用等。
下一篇我们深入学习一下Mybatis的架构设计,源码分析以及涉及到的Java设计模式。
转载地址:http://tesui.baihongyu.com/