标签:插件 缓存 id 知识 public mybatis Mybatis 梳理
目录
一.Mybatis架构原理
1.Mybatis入门该网站总结的非常详细快速回顾下基础
http://c.biancheng.net/view/4309.html
2.Mybatis架构原理
2.1架构设计
我们把Mybatis的功能架构分为三层:
(1) API接口层:提供给外部使用的接口 API,开发人员通过这些本地API来操纵数据库。接口层一接收到 调用请求就会调用数据处理层来完成具体的数据处理。 MyBatis和数据库的交互有两种方式: a. 使用传统的MyBati s提供的API ; b. 使用Mapper代理的方式
(2) 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根 据调用的请求完成一次数据库操作。
(3) 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是 共 用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
2.2主要构件及其相互关系
构建 | 描述 |
SqlSession | 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能 |
Executor | MyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查询缓 存的维护 |
StatementHandler | 封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将Statement结果集转换成List集合。 |
ParameterHandler | 负责对用户传递的参数转换成JDBC Statement所需要的参数 |
ResultSetHandler | 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合 |
TypeHandler | 负责java数据类型和jdbc数据类型之间的映射和转换 |
MappedStatement | MappedStatement维护了一条<select|update|insert|delete>节点的封装 |
SqlSouce | 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封 |
BoundSql | 表示动态生成的SQL语句以及相应的参数信息 |
图解:
2.3总体流程
- 加载配置并初始化 触发条件:加载配置文件 配置来源于两个地方,一个是配置文件(主配置文件conf.xml,mapper文件*.xml),—个是java代码中的 注 解,将主配置文件内容解析封装到Configuration,将sql的配置信息加载成为一个mappedstatement 对 象,存储在内存之中
- 接收调用请求 触发条件:调用Mybatis提供的API 传入参数:为SQL的ID和传入参数对象 处理过程:将请求传递给下层的请求处理层进行处理。
- 处理操作请求 触发条件:API接口层传递请求过来 传入参数:为SQL的ID和传入参数对象 处理过程: (A) 根据SQL的ID查找对应的MappedStatement对象。 (B) 根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。 (C) 获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果。 (D) 根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理 结果。 (E) 释放连接资源。
- 返回处理结果 将最终的处理结果返回。
二.Mybatis缓存
(1)一级缓存原理探究与源码分析
一级缓存概念理解
例如 一张用户表User包含id,name字段:
1、第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从 数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
2、 如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的 一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
3、 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息
一级缓存源码分析
一级缓存也绕不开SqlSession,如下图:
看了一下,发现上述所有方法中,好像只有clearCache()和缓存沾点关系,那直接从这个方法入手吧,分析源码时,我们要看它(此类)是谁,它的父类和子类分别又是谁,对如上关系了解了,你才会对这个类有更深的认识,分析了一圈,你可能会得到如下这个流程图:
再深入分析,流程走到Perpetualcache中的clear()方法之后,会调用其cache.clear()方法,那 么这个cache是什么东西呢?点进去发现,cache其实就是private Map cache = new HashMap();也就是一个Map,所以说cache.clear()其实就是map.clear(),也就是说,缓存其实就是 本地存放的一个map对象,每一个SqISession都会存放一个map对象的引用,那么这个cache是何 时创建的呢?
你觉得最有可能创建缓存的地方是哪里呢?我觉得是Executor,为什么这么认为?因为Executor是 执行器,用来执行SQL请求,而且清除缓存的方法也在Executor中执行,所以很可能缓存的创建也很 有可能在Executor中,看了一圈发现Executor中有一个createCacheKey方法,这个方法很像是创 建缓存的方法啊,跟进去看看,你发现createCacheKey方法是由BaseExecutor执行的,代码如下
CacheKey cacheKey = new CacheKey(); //MappedStatement 的 id // id就是Sql语句的所在位置包名+类名+ SQL名称 cacheKey.update(ms.getId()); // offset 就是 0 cacheKey.update(rowBounds.getOffset()); // limit 就是 Integer.MAXVALUE cacheKey.update(rowBounds.getLimit()); //具体的SQL语句 cacheKey.update(boundSql.getSql()); //后面是update 了 sql中带的参数 cacheKey.update(value); ... if (configuration.getEnvironment() != null) { // issue #176 cacheKey.update(configuration.getEnvironment().getId()); } |
创建缓存key会经过一系列的update方法,udate方法由一个CacheKey这个对象来执行的,这个update方法最终由updateList的list来把五个值存进去,对照上面的代码和下面的图示,你应该能 理解这五个值都是什么了。
这里需要注意一下最后一个值,configuration.getEnvironment().getId()这是什么,这其实就是 定义在mybatis-config.xml中的标签,见如下
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> |
那么我们回归正题,那么创建完缓存之后该用在何处呢?总不会凭空创建一个缓存不使用吧?绝对不会的,经过我们对一级缓存的探究之后,我们发现一级缓存更多是用于查询操作,毕竟一级缓存也叫做查询缓存吧,为什么叫查询缓存我们一会儿说。我们先来看一下这个缓存到底用在哪了,我们跟踪到query方法如下:
Override public <E> List<E> 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); } @SuppressWarnings("unchecked") Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ... 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); } ... } // queryFromDatabase 方法 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; } |
如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进行写入。 localcache对象的put方法最终交给Map进行存放。
private Map<Object, Object> cache = new HashMap<Object, Object>(); @Override public void putObject(Object key, Object value) { cache.put(key, value); } |
(2)二级缓存
二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于sqlSession的,而二级缓存是基于mapper文件的namespace的,也 就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace 相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中
如何使用二级缓存
一、开启二级缓存
和一级缓存默认开启不一样,二级缓存需要我们手动开启
首先在全局配置文件sqlMapConfig.xml文件中加入如下代码
<!--开启二级缓存--> <settings> <setting name="cacheEnabled" value="true"/> </settings> |
其次在例如:UserMapper.xml文件中开启缓存
<!--开启二级缓存--> <cache></cache> |
我们可以看到mapper.xml文件中就这么一个空标签,其实这里可以配置,PerpetualCache这个类是mybatis默认实现缓存功能的类。我们不写type就使用mybatis默认的缓存,也可以去实现Cache接口来自定义缓存
public class PerpetualCache implements Cache { private final String id; private MapcObject, Object> cache = new HashMapC); public PerpetualCache(St ring id) { this.id = id; } |
我们可以看到二级缓存底层还是HashMap结构
public class User implements Serializable( //用户ID private int id; //用户姓名 private String username; //用户性别 private String sex; } |
开启了二级缓存后,还需要将要缓存的pojo实现Serializable接口,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再取这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接口。
二、useCache和flushCache
mybatis中还可以配置userCache和flushCache等配置项,userCache是用来设置是否禁用二级缓 存的,在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出 sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="selectUserByUserId" useCache="false" resultType="com.lagou.pojo.User" parameterType="int"> select * from user where id=#{id} </select> |
这种情况是针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存,直接从数据库中获取。
在mapper的同一个namespace中,如果有其它insert、update, delete操作数据后需要刷新缓 存,如果不执行刷新缓存会出现脏读。
设置statement配置中的flushCache="true”属性,默认情况下为true,即刷新缓存,如果改成false则 不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
<select id="selectUserByUserId" flushCache="true" useCache="false" resultType="com.lagou.pojo.User" parameterType="int"> select * from user where id=#{id} </select> |
一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。所以我们不用设置,默认即可。
- Redis整合二级缓存
基础:
上面我们介绍了 mybatis自带的二级缓存,但是这个缓存是单服务器工作,无法实现分布式缓存。 那么什么是分布式缓存呢?假设现在有两个服务器1和2,用户访问的时候访问了 1服务器,查询后的缓 存就会放在1服务器上,假设现在有个用户访问的是2服务器,那么他在2服务器上就无法获取刚刚那个 缓存,
如下图所示:
为了解决这个问题,就得找一个分布式的缓存,专门用来存储缓存数据的,这样不同的服务器要缓存数据都往它那里存,取缓存数据也从它那里取,如下图所示:
如上图所示,在几个不同的服务器之间,我们使用第三方缓存框架,将缓存都放在这个第三方框架中, 然后无论有多少台服务器,我们都能从缓存中获取数据。
这里我们介绍mybatis与redis的整合。刚刚提到过,mybatis提供了一个eache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。mybatis本身默认实现了一个,但是这个缓存的实现无法实现分布式缓存,所以我们要自己来实现。
redis分布式缓存就可以,mybatis提供了一个针对cache接口的redis实现类,该类存在mybatis-redis包中。
实现:
- pom文件
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-redis</artifactId> <version>1.0.0-beta2</version> </dependency> |
2.配置文件
Mapper.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.lagou.mapper.IUserMapper"> <cache type="org.mybatis.caches.redis.RedisCache" /> <select id="findAll" resultType="com.lagou.pojo.User" useCache="true"> select * from user </select> |
3.Redis.properties
redis.host=localhost redis.port=6379 redis.connectionTimeout=5000 redis.password= redis.database=0 |
4.测试
@Test public void SecondLevelCache(){ SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); SqlSession sqlSession3 = sqlSessionFactory.openSession(); IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class); lUserMapper mapper2 = sqlSession2.getMapper(lUserMapper.class); lUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class); User user1 = mapper1.findUserById(1); sqlSession1.close(); //清空一级缓存 User user = new User(); user.setId(1); user.setUsername("lisi"); mapper3.updateUser(user); sqlSession3.commit(); User user2 = mapper2.findUserById(1); System.out.println(user1==user2); } |
源码分析:
RedisCache和大家普遍实现Mybatis的缓存方案大同小异,无非是实现Cache接口,并使用jedis操作缓存;不过该项目在设计细节上有一些区别;
public final class RedisCache implements Cache { public RedisCache(final String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require anID"); } this.id = id; RedisConfig redisConfig = RedisConfigurationBuilder.getInstance().parseConfiguration(); pool = new JedisPool(redisConfig, redisConfig.getHost(), redisConfig.getPort(), redisConfig.getConnectionTimeout(), redisConfig.getSoTimeout(), redisConfig.getPassword(), redisConfig.getDatabase(), redisConfig.getClientName()); } |
RedisCache在mybatis启动的时候,由MyBatis的CacheBuilder创建,创建的方式很简单,就是调用RedisCache的带有String参数的构造方法,即RedisCache(String id);而在RedisCache的构造方法中,调用了 RedisConfigurationBuilder 来创建 RedisConfig 对象,并使用 RedisConfig 来创建JedisPool。
RedisConfig类继承了 JedisPoolConfig,并提供了 host,port等属性的包装,简单看一下RedisConfig的属性:
public class RedisConfig extends JedisPoolConfig { private String host = Protocol.DEFAULT_HOST; private int port = Protocol.DEFAULT_PORT; private int connectionTimeout = Protocol.DEFAULT_TIMEOUT; private int soTimeout = Protocol.DEFAULT_TIMEOUT; private String password; private int database = Protocol.DEFAULT_DATABASE; private String clientName; |
RedisConfig对象是由RedisConfigurationBuilder创建的,简单看下这个类的主要方法:
public RedisConfig parseConfiguration(ClassLoader classLoader) { Properties config = new Properties(); InputStream input = classLoader.getResourceAsStream(redisPropertiesFilename); if (input != null) { try { config.load(input); } catch (IOException e) { throw new RuntimeException( "An error occurred while reading classpath property '" + redisPropertiesFilename + "', see nested exceptions", e); } finally { try { input.close(); } catch (IOException e) { // close quietly } } } RedisConfig jedisConfig = new RedisConfig(); setConfigProperties(config, jedisConfig); return jedisConfig; } |
核心的方法就是parseConfiguration方法,该方法从classpath中读取一个redis.properties文件:
host=localhost port=6379 connectionTimeout=5000 soTimeout=5000 password= database=0 clientName= |
并将该配置文件中的内容设置到RedisConfig对象中,并返回;接下来,就是RedisCache使用RedisConfig类创建完成JedisPool;在RedisCache中实现了一个简单的模板方法,用来操作Redis:
private Object execute(RedisCallback callback) { Jedis jedis = pool.getResource(); try { return callback.doWithRedis(jedis); } finally { jedis.close(); } } |
模板接口为RedisCallback,这个接口中就只需要实现了一个doWithRedis方法而已:
public interface RedisCallback { Object doWithRedis(Jedis jedis); } |
接下来看看Cache中最重要的两个方法:putObject和getObject,通过这两个方法来查看mybatis-redis储存数据的格式:
@Override public void putObject(final Object key, final Object value) { execute(new RedisCallback() { @Override public Object doWithRedis(Jedis jedis) { jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value)); return null; } }); } @Override public Object getObject(final Object key) { return execute(new RedisCallback() { @Override public Object doWithRedis(Jedis jedis) { return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(), key.toString().getBytes())); } }); } |
可以很清楚的看到,mybatis-redis在存储数据的时候,是使用的hash结构,把cache的id作为这个hash的key (cache的id在mybatis中就是mapper的namespace);这个mapper中的查询缓存数据作为 hash的field,需要缓存的内容直接使用SerializeUtil存储,SerializeUtil和其他的序列化类差不多,负责对象的序列化和反序列化;
三.Mybatis延迟加载
(1)延迟加载原理
延迟加载:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
1.Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
2.它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
四.Mybatis插件
- 插件原理
插件简介
一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展。这样的好处是显而易见的,一是增加了框架的灵活性。二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作。以MyBatis为例,我们可基于MyBati s插件机制实现分页、分表,监控等功能。由于插件和业务无关,业务也无法感知插件的存在。因此可以无感植入插件,在无形中增强功能。
Mybatis插件介绍
Mybatis作为一个应用广泛的优秀的ORM开源框架,这个框架具有强大的灵活性,在四大组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易用的插 件扩展机制。Mybatis对持久层的操作就是借助于四大核心对象。MyBatis支持用插件对四大核心对象进 行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,
增强功能本质上是借助于底层的 动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象。
MyBatis所允许拦截的方法如下:
执行器Executor (update、query、commit、rollback等方法);
SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等方 法);
参数处理器ParameterHandler (getParameterObject、setParameters方法);
结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);
Mybatis插件原理
在四大对象创建的时候
1、每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);
2、获取到所有的Interceptor (拦截器)(插件需要实现的接口);调用 interceptor.plugin(target);返回 target 包装后的对象
3、插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP (面向切面)我们的插件可 以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行
拦截
插件具体是如何拦截并附加额外的功能的呢?以ParameterHandler来说
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object object, BoundSql sql, InterceptorChain interceptorChain){ ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } |
interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的。调用拦截器链中的拦截器依次的对目标进行拦截或增强。interceptor.plugin(target)中的target就可以理解为mybatis中的四大对象。返回的target是被重重代理后的对象
如果我们想要拦截Executor的query方法,那么可以这样定义插件:
@Intercepts({ @Signature( type = Executor.class, method = "query", args= {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class} ) }) public class ExeunplePlugin implements Interceptor { //省略逻辑 } |
除此之外,我们还需将插件配置到sqlMapConfig.xm l中。
<plugins> <plugin interceptor="com.lagou.plugin.ExamplePlugin"> </plugin> </plugins> |
这样MyBatis在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链) 中。待准备工作做完后,MyBatis处于就绪状态。我们在执行SQL时,需要先通过DefaultSqlSessionFactory 创建 SqlSession。Executor 实例会在创建 SqlSession 的过程中被创建, Executor实例创建完毕后,MyBatis会通过JDK动态代理为实例生成代理类。这样,插件逻辑即可在 Executor相关方法被调用前执行。以上就是MyBatis插件机制的基本原理
- 编写一个Myabtis插件
插件接口
Mybatis 插件接口-Interceptor
• Intercept方法,插件的核心方法
• plugin方法,生成target的代理对象
• setProperties方法,传递插件所需参数
自定义插件
设计实现一个自定义插件
Intercepts ({//注意看这个大花括号,也就这说这里可以定义多个@Signature对多个地方拦截,都用这 个拦截器 @Signature (type = StatementHandler .class , //这是指拦截哪个接口 method = "prepare",//这个接口内的哪个方法名,不要拼错了 args = { Connection.class, Integer .class}), 这是拦截的方法的入参,按顺 序写到这,不要多也不要少,如果方法重载,可是要通过方法名和入参来确定唯一的 }) public class MyPlugin implements Interceptor { private final Logger logger = LoggerFactory.getLogger(this.getClass()); // //这里是每次执行操作的时候,都会进行这个拦截器的方法内 Override public Object intercept(Invocation invocation) throws Throwable { //增强逻辑 System.out.println("对方法进行了增强...."); return invocation.proceed(); //执行原方法 } /** * //主要是为了把这个拦截器生成一个代理放到拦截器链中 * ^Description包装目标对象 为目标对象创建代理对象 * @Param target为要拦截的对象 * @Return代理对象 */ Override public Object plugin(Object target) { System.out.println("将要包装的目标对象:"+target); return Plugin.wrap(target,this); }
/**获取配置文件的属性**/ //插件初始化的时候调用,也只调用一次,插件配置的属性从这里设置进来 Override public void setProperties(Properties properties) { System.out.println("插件配置的初始化参数:"+properties ); } } |
sqlMapConfig.xml
<plugins> <plugin interceptor="com.lagou.plugin.MySqlPagingPlugin"> <!--配置参数--> <property name="name" value="Bob"/> </plugin> </plugins |
mapper接口
public interface UserMapper { List<User> selectUser(); } |
Mapper.xml
<mapper namespace="com.lagou.mapper.UserMapper"> <select id="selectUser" resultType="com.lagou.pojo.User"> SELECT id,username FROM user </select> </mapper> |
测试类
public class PluginTest { @Test public void test() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> byPaging = userMapper.selectUser(); for (User user : byPaging) { System.out.println(user); } } } |
- pageHelper分页插件
MyBati s可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封
装,使用简单的方式即可获得分页的相关数据
开发步骤:
① 导入通用PageHelper的坐标
② 在mybatis核心配置文件中配置PageHelper插件
③ 测试分页数据获取
①导入通用PageHelper坐标
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.5</version> </dependency> <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>0.9.1</version> </dependency> |
② 在mybatis核心配置文件中配置PageHelper插件
<!--注意:分页助手的插件 配置在通用馆mapper之前*-->* <plugin interceptor="com.github.pagehelper.PageHelper"> <!—指定方言 —> <property name="dialect" value="mysql"/> </plugin> |
③ 测试分页代码实现
@Test public void testPageHelper() { //设置分页参数 PageHelper.startPage(1, 2); List<User> select = userMapper2.select(null); for (User user : select) { System.out.println(user); } } } |
获得分页相关的其他参数
//其他分页的数据 PageInfo<User> pageInfo = new PageInfo<User>(select); System.out.println("总条数:"+pageInfo.getTotal()); System.out.println("总页数:"+pageInfo. getPages ()); System.out.println("当前页:"+pageInfo. getPageNum()); System.out.println("每页显万长度:"+pageInfo.getPageSize()); System.out.println("是否第一页:"+pageInfo.isIsFirstPage()); System.out.println("是否最后一页:"+pageInfo.isIsLastPage()); |
(4)通用 mapper
什么是通用Mapper
通用Mapper就是为了解决单表增删改查,基于Mybatis的插件机制。开发人员不需要编写SQL,不需要在DAO中增加方法,只要写好实体类,就能支持相应的增删改查方法
如何使用
- 首先在maven项目,在pom.xml中引入mapper的依赖
<dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper</artifactId> <version>3.1.2</version> </dependency> |
- Mybatis配置文件中完成配置
<plugins> <!--分页插件:如果有分页插件,要排在通用mapper之前--> <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="mysql"/> </plugin> <plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor"> <!-- 通用Mapper接口,多个通用接口用逗号隔开 --> <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/> </plugin> </plugins> |
- 实体类设置主键
@Table(name = "t_user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String username; } |
- 定义通用mapper
import com.lagou.domain.User; import tk.mybatis.mapper.common.Mapper; public interface UserMapper extends Mapper<User> { } |
- 测试
public class UserTest { @Test public void test1() throws IOException { Inputstream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = build.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setId(4); //(1)mapper基础接口 //select 接口 User user1 = userMapper.selectOne(user); //根据实体中的属性进行查询,只能有 —个返回值 List<User> users = userMapper.select(null); //查询全部结果 userMapper.selectByPrimaryKey(1); //根据主键字段进行查询,方法参数必须包含完 整的主键属性,查询条件使用等号 userMapper.selectCount(user); //根据实体中的属性查询总数,查询条件使用等号 // insert 接口 int insert = userMapper.insert(user); //保存一个实体,null值也会保存,不会使用数据库默认值 int i = userMapper.insertSelective(user); //保存实体,null的属性不会保存, 会使用数据库默认值 // update 接口 int i1 = userMapper.updateByPrimaryKey(user);//根据主键更新实体全部字段,null值会被更新 // delete 接口 int delete = userMapper.delete(user); //根据实体属性作为条件进行删除,查询条件使用等号 userMapper.deleteByPrimaryKey(1); //根据主键字段进行删除,方法参数必须包含完 整的主键属性 //(2)example方法 Example example = new Example(User.class); example.createCriteria().andEqualTo("id", 1); example.createCriteria().andLike("val", "1"); //自定义查询 List<User> users1 = userMapper.selectByExample(example); } } |
标签:插件,缓存,id,知识,public,mybatis,Mybatis,梳理 来源: https://blog.csdn.net/illovesoftware/article/details/117474960
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。