MyBatis的基本应用
源码地址
01.MyBatis环境搭建
-
添加MyBatis的坐标
<!--mybatis坐标--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.9</version></dependency><!--mysql驱动坐标--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version><scope>runtime</scope></dependency><!--单元测试坐标--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version></dependency>
-
创建数据表
-
编写DO实体类
-
编写映射⽂件UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.muchfish.dao.IUserDao"><!--namespace : 名称空间:与id组成sql的唯一标识resultType: 表明返回值类型--><!--查询用户--><select id="findAll" resultType="com.muchfish.pojo.User">select * from User</select></mapper>
-
编写核⼼⽂件SqlMapConfig.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><!--加载外部的properties文件--><properties resource="jdbc.properties"/><!--environments:运行环境--><environments default="development"><environment id="development"><!--当前事务交由JDBC进行管理--><transactionManager type="JDBC"/><!--当前使用mybatis提供的连接池--><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><!--引入映射配置文件--><mappers><mapper resource="UserMapper.xml"/></mappers></configuration>
jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql:///mybatis jdbc.username=root jdbc.password=123456
-
编写测试类
@Testpublic void test1() throws IOException {//1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");//2.解析了配置文件,并创建了sqlSessionFactory工厂SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);//3.生产sqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交//在进行增删改操作时,要手动提交事务//4.sqlSession调用方法:查询所有selectList 查询单个:selectOne 添加:insert 修改:update 删除:deleteList<User> users = sqlSession.selectList("com.muchfish.dao.IUserDao.findAll");for (User user : users) {System.out.println(user);}sqlSession.close();}
02.MyBatis的CRUD
-
CRUD的API
- sqlSession.selectList()、sqlSession.selectOne()
- sqlSession.insert()
- sqlSession.update()
- sqlSession.delete()
-
注意问题
-
在进行增删改操作时,要手动提交事务。
sqlSessionFactory.openSession()默认开启一个事务,但是该事务不会自动提交
-
mapper.xml中的Sql语句中使⽤#{任意字符串}⽅式引⽤传递的单个参数
<!--删除--> <delete id="deleteUser" parameterType="int">delete from user where id = #{abc} </delete>
-
sqlSession.close():释放资源
-
sqlSession.commit()
-
sqlSession.rollback()
-
sqlSessionFactory.openSession(true):事务自动提交
-
03.MyBatis相关配置文件
sqlMapConfig.xml
-
mapper标签
该标签的作⽤是加载映射的,加载⽅式有如下⼏种:
•使⽤相对于类路径的资源引⽤,例如: <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>•使⽤完全限定资源定位符(URL),例如: <mapper url="file:///var/mappers/AuthorMapper.xml"/>•使⽤映射器接⼝实现类的完全限定类名,例如: <mapper class="org.mybatis.builder.AuthorMapper"/> 注意:保证接口名和xml文件名一致且包结构一致(是否包结构一直皆可,文件名可以不一致)•将包内的映射器接⼝实现全部注册为映射器,例如: <package name="org.mybatis.builder"/> 注意:保证接口名和xml文件名一致且包结构一致。(是否包结构一直皆可,文件名可以不一致。测试得:文件名也必须一致)
XXXmapper.xml
04.MyBatis的Dao层代理开发方式与mappers标签测试
mappers标签测试
•使⽤映射器接⼝实现类的完全限定类名,例如:
<mapper class="com.muchfish.dao.IUserDao"/>
注意:保证接口名和xml文件名一致且包结构一致•将包内的映射器接⼝实现全部注册为映射器,例如:
<package name="com.muchfish.dao"/>
注意:保证接口名和xml文件名一致且包结构一致。
Dao层代理开发
Mapper 接⼝开发需要遵循以下规范:
- Mapper.xml⽂件中的namespace与mapper接⼝的全限定名相同
- Mapper接⼝⽅法名和Mapper.xml中定义的每个statement的id相同
- Mapper接⼝⽅法的输⼊参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
- Mapper接⼝⽅法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
05.MyBatis的多对多复杂映射
-
DO类
public class User {private Integer id;private String username;private String password;private String birthday;//表示用户关联的角色private List<Role> roleList = new ArrayList<>();//。。。省略getter/setter }
-
Mapper.xml
<resultMap id="userRoleMap" type="com.muchfish.pojo.User"><result property="id" column="userid"></result><result property="username" column="username"></result><collection property="roleList" ofType="com.muchfish.pojo.Role"><result property="id" column="roleid"></result><result property="roleName" column="roleName"></result><result property="roleDesc" column="roleDesc"></result></collection></resultMap><select id="findAllUserAndRole" resultMap="userRoleMap">select * from user u left join sys_user_role ur on u.id = ur.useridleft join sys_role r on r.id = ur.roleid</select>
-
Dao接口
public interface IUserDao {public List<User> findAllUserAndRole(); }
06.MyBatis的注解开发
-
MyBatis的常⽤注解
- @Insert:实现新增
- @Update:实现更新
- @Delete:实现删除
- @Select:实现查询
- @Result:实现结果集封装
- @Results:可以与@Result ⼀起使⽤,封装多个结果集
- @One:实现⼀对⼀结果集封装
- @Many:实现⼀对多结果集封装
-
注解一对多查询
public interface IOrderDao {//查询订单的同时还查询该订单所属的用户@Results({@Result(property = "id",column = "id"),@Result(property = "orderTime",column = "orderTime"),@Result(property = "total",column = "total"),@Result(property = "user",column = "uid",javaType = User.class,one=@One(select = "com.muchfish.dao.IUserDao.findUserById"))})@Select("select * from orders")public List<Order> findOrderAndUser();@Select("select * from orders where uid = #{uid}")public List<Order> findOrderByUid(Integer uid);}
- 注解和xml混合使用命中同一个statementId会报错
07.MyBatis缓存
⼀级缓存
private IUserDao userMapper;
private SqlSession sqlSession;
private SqlSessionFactory sqlSessionFactory;@Before
public void before() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);sqlSession = sqlSessionFactory.openSession();userMapper = sqlSession.getMapper(IUserDao.class);}@Test
public void test1() {//第⼀次查询,发出sql语句,并将查询出来的结果放进缓存中User u1 = userMapper.findUserById(1);System.out.println(u1);//第⼆次查询,由于是同⼀个sqlSession,会在缓存中查询结果//如果有,则直接从缓存中取出来,不和数据库进⾏交互User u2 = userMapper.findUserById(1);System.out.println(u2);sqlSession.close();
}
查看控制台打印情况:
@Testpublic void test2(){//根据 sqlSessionFactory 产⽣ session//第⼀次查询,发出sql语句,并将查询的结果放⼊缓存中User u1 = userMapper.findUserById( 1 );System.out.println(u1);//第⼆步进⾏了⼀次更新操作, sqlSession.commit()u1.setPassword("23131");userMapper.updateUserByUserId(u1);sqlSession.commit();//第⼆次查询,由于是同⼀个sqlSession.commit(),会清空缓存信息//则此次查询也会发出sql语句User u2 = userMapper.findUserById(1);System.out.println(u2);sqlSession.close();}
查看控制台打印情况:
日志略:第⼆次查询会打印sql语句
- 总结
- 第⼀次发起查询⽤户id为1的⽤户信息,先去找缓存中是否有id为1的⽤户信息,如果没有,从 数据库查询⽤户信息。得到⽤户信息,将⽤户信息存储到⼀级缓存中。
- 如果中间sqlSession去执⾏commit操作(执⾏插⼊、更新、删除),则会清空SqlSession中的 ⼀级缓存,这样做的⽬的为了让缓存中存储的是最新的信息,避免脏读。
- 第⼆次发起查询⽤户id为1的⽤户信息,先去找缓存中是否有id为1的⽤户信息,缓存中有,直 接从缓存中获取⽤户信息
二级缓存
-
使用二级缓存
-
开启⼆级缓存
-
在全局配置⽂件sqlMapConfig.xml⽂件中开启
<!--开启⼆级缓存 注意<settings>标签的顺序,在<properties>标签后面--> <settings> <setting name="cacheEnabled" value="true"/> </settings>
-
在Mapper.xml⽂件中开启缓存
<!--使用二级缓存--><cache></cache>
-
在DAO接口中开启缓存(可选。使用@CacheNamespace会报错。)
@CacheNamespace public interface IUserDao {}
或
@CacheNamespaceRef(IUserDao.class) //@CacheNamespace public interface IUserDao {}
- xml和对应dao接口上同时开启二级缓存会报错,此时只能使用@CacheNamespaceRef
-
-
DO序列化
public class User implements Serializable {//。。。 }
-
测试
@Testpublic void SecondLevelCache(){//根据 sqlSessionFactory 产⽣ sessionSqlSession sqlSession1 = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();SqlSession sqlSession3 = sqlSessionFactory.openSession();String statement = "com.lagou.pojo.UserMapper.selectUserByUserld" ;IUserDao userMapper1 = sqlSession1.getMapper(IUserDao. class );IUserDao userMapper2 = sqlSession2.getMapper(IUserDao. class );IUserDao userMapper3 = sqlSession2.getMapper(IUserDao. class );//第⼀次查询,发出sql语句,并将查询的结果放⼊缓存中User u1 = userMapper1.findById( 1 );System.out.println(u1);sqlSession1.close(); //第⼀次查询完后关闭sqlSession//执⾏更新操作, commit()。注释掉此处,sqlSession2的查询会走缓存。放开此处,会走数据库 // u1.setUsername( "aaa" ); // userMapper3.updateUserByUserId(u1); // sqlSession3.commit();//第⼆次查询,由于上次更新操作,缓存数据已经清空(防⽌数据脏读),这⾥必须再次发出sql语User u2 = userMapper2.findById( 1 );System.out.println(u2);sqlSession2.close();}
-
-
useCache和flushCache
-
useCache:开启或禁用缓存
<select id="findById" resultType="com.muchfish.pojo.User" useCache="true">select * from user where id = #{id}</select>
-
flushCache:刷新缓存
<select id="findById" resultType="com.muchfish.pojo.User" useCache="true" flushCache="true">select * from user where id = #{id}</select>
-
二级缓存整合redis
-
pom⽂件
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-redis</artifactId><version>1.0.0-beta2</version></dependency>
-
配置⽂件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.muchfish.dao.IUserDao"><!--namespace : 名称空间:与id组成sql的唯一标识resultType: 表明返回值类型--><!--使用二级缓存--><cache type="org.mybatis.caches.redis.RedisCache" />
-
redis.properties
redis.host=localhost redis.port=6379 redis.connectionTimeout=5000 redis.password= redis.database=0
-
测试
@Testpublic void xmlSecondLevelCache(){//根据 sqlSessionFactory 产⽣ sessionSqlSession sqlSession1 = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();SqlSession sqlSession3 = sqlSessionFactory.openSession();String statement = "com.lagou.pojo.UserMapper.selectUserByUserld" ;IUserDao userMapper1 = sqlSession1.getMapper(IUserDao. class );IUserDao userMapper2 = sqlSession2.getMapper(IUserDao. class );IUserDao userMapper3 = sqlSession2.getMapper(IUserDao. class );//第⼀次查询,发出sql语句,并将查询的结果放⼊缓存中User u1 = userMapper1.findById( 1 );System.out.println(u1);sqlSession1.close(); //第⼀次查询完后关闭sqlSession//执⾏更新操作, commit()。注释掉此处,sqlSession2的查询会走缓存。放开此处,会走数据库 // u1.setUsername( "aaa" ); // userMapper3.updateUserByUserId(u1); // sqlSession3.commit();//第⼆次查询,由于上次更新操作,缓存数据已经清空(防⽌数据脏读),这⾥必须再次发出sql语User u2 = userMapper2.findById( 1 );System.out.println(u2);sqlSession2.close();}
-
RedisCache实现原理
/*** Cache adapter for Redis.** @author Eduardo Macarron*/ public final class RedisCache implements Cache {private final ReadWriteLock readWriteLock = new DummyReadWriteLock();private String id;private static JedisPool pool;public RedisCache(final String id) {if (id == null) {throw new IllegalArgumentException("Cache instances require an ID");}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());}private Object execute(RedisCallback callback) {Jedis jedis = pool.getResource();try {return callback.doWithRedis(jedis);} finally {jedis.close();}}@Overridepublic String getId() {return this.id;}@Overridepublic int getSize() {return (Integer) execute(new RedisCallback() {@Overridepublic Object doWithRedis(Jedis jedis) {Map<byte[], byte[]> result = jedis.hgetAll(id.toString().getBytes());return result.size();}});}@Overridepublic void putObject(final Object key, final Object value) {execute(new RedisCallback() {@Overridepublic Object doWithRedis(Jedis jedis) {jedis.hset(id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));return null;}});}@Overridepublic Object getObject(final Object key) {return execute(new RedisCallback() {@Overridepublic Object doWithRedis(Jedis jedis) {return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(), key.toString().getBytes()));}});}@Overridepublic Object removeObject(final Object key) {return execute(new RedisCallback() {@Overridepublic Object doWithRedis(Jedis jedis) {return jedis.hdel(id.toString(), key.toString());}});}@Overridepublic void clear() {execute(new RedisCallback() {@Overridepublic Object doWithRedis(Jedis jedis) {jedis.del(id.toString());return null;}});}@Overridepublic ReadWriteLock getReadWriteLock() {return readWriteLock;}@Overridepublic String toString() {return "Redis {" + id + "}";}}
/*** Converter from the Config to a proper {@link RedisConfig}.** @author Eduardo Macarron*/ final class RedisConfigurationBuilder {/*** This class instance.*/private static final RedisConfigurationBuilder INSTANCE = new RedisConfigurationBuilder();private static final String SYSTEM_PROPERTY_REDIS_PROPERTIES_FILENAME = "redis.properties.filename";private static final String REDIS_RESOURCE = "redis.properties";private final String redisPropertiesFilename;/*** Hidden constructor, this class can't be instantiated.*/private RedisConfigurationBuilder() {redisPropertiesFilename = System.getProperty(SYSTEM_PROPERTY_REDIS_PROPERTIES_FILENAME, REDIS_RESOURCE);}/*** Return this class instance.** @return this class instance.*/public static RedisConfigurationBuilder getInstance() {return INSTANCE;}/*** Parses the Config and builds a new {@link RedisConfig}.** @return the converted {@link RedisConfig}.*/public RedisConfig parseConfiguration() {return parseConfiguration(getClass().getClassLoader());}/*** Parses the Config and builds a new {@link RedisConfig}.** @param the* {@link ClassLoader} used to load the* {@code memcached.properties} file in classpath.* @return the converted {@link RedisConfig}.*/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;}private void setConfigProperties(Properties properties,RedisConfig jedisConfig) {if (properties != null) {MetaObject metaCache = SystemMetaObject.forObject(jedisConfig);for (Map.Entry<Object, Object> entry : properties.entrySet()) {String name = (String) entry.getKey();String value = (String) entry.getValue();if (metaCache.hasSetter(name)) {Class<?> type = metaCache.getSetterType(name);if (String.class == type) {metaCache.setValue(name, value);} else if (int.class == type || Integer.class == type) {metaCache.setValue(name, Integer.valueOf(value));} else if (long.class == type || Long.class == type) {metaCache.setValue(name, Long.valueOf(value));} else if (short.class == type || Short.class == type) {metaCache.setValue(name, Short.valueOf(value));} else if (byte.class == type || Byte.class == type) {metaCache.setValue(name, Byte.valueOf(value));} else if (float.class == type || Float.class == type) {metaCache.setValue(name, Float.valueOf(value));} else if (boolean.class == type || Boolean.class == type) {metaCache.setValue(name, Boolean.valueOf(value));} else if (double.class == type || Double.class == type) {metaCache.setValue(name, Double.valueOf(value));} else {throw new CacheException("Unsupported property type: '"+ name + "' of type " + type);}}}}}}
-
小结
- 二级缓存使用redis可以实现分布式缓存
- 自定义实现二级缓存,通过实现Cache接口
RedisCache
通过RedisConfigurationBuilder
加载redis.properties
中的配置RedisCache
使用了JedisPool
RedisCache
使用了模板方法,完成putObject
、getObject
、removeObject
、clear
等操作
08.MyBatis插件
Mybatis插件原理
-
注册插件
//XMLConfigBuilder类的parseConfiguration方法 (解析SqlMapConfig.xml时) private void parseConfiguration(XNode root) {try {// issue #117 read properties firstpropertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);typeAliasesElement(root.evalNode("typeAliases"));//解析插件 pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}
private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {String interceptor = child.getStringAttribute("interceptor");Properties properties = child.getChildrenAsProperties();Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();interceptorInstance.setProperties(properties);//注册插件configuration.addInterceptor(interceptorInstance);}}}
public void addInterceptor(Interceptor interceptor) {interceptorChain.addInterceptor(interceptor);}
public class InterceptorChain {//插件列表private final List<Interceptor> interceptors = new ArrayList<>();//应用插件public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;}//注册插件public void addInterceptor(Interceptor interceptor) {interceptors.add(interceptor);}public List<Interceptor> getInterceptors() {return Collections.unmodifiableList(interceptors);}}
- 通过Bean注入的方式注册拦截器的方式代码不在此处
-
应用插件
//Configuration类中public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);//应用插件,拦截ParameterHandlerparameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);return parameterHandler;}public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);//应用插件,拦截ResultSetHandlerresultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;}public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);//应用插件,拦截StatementHandlerstatementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}public Executor newExecutor(Transaction transaction) {return newExecutor(transaction, defaultExecutorType);}public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}if (cacheEnabled) {executor = new CachingExecutor(executor);}//应用插件,拦截Executorexecutor = (Executor) interceptorChain.pluginAll(executor);return executor;}
public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<>();//应用拦截器,对目标对象进行拦截。target为ParameterHandler、ResultSetHandler、StatementHandler、Executorpublic Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {//链式拦截//遍历所有插件,对target进行拦截后,返回值为target,target将被下一个拦截器拦截target = interceptor.plugin(target);}return target;} }
/*** @author Clinton Begin*/ public interface Interceptor {Object intercept(Invocation invocation) throws Throwable;//拦截器拦截default Object plugin(Object target) {//拦截器拦截return Plugin.wrap(target, this);}default void setProperties(Properties properties) {// NOP}}
-
生成插件拦截代理类
//Plugin类中 public static Object wrap(Object target, Interceptor interceptor) {//1.构建签名映射表signatureMap。//记录拦截器所关注的方法签名及其对应的拦截逻辑Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);//2.确定目标对象接口Class<?> type = target.getClass();//找出所有需要被代理的接口Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//3.创建代理对象if (interfaces.length > 0) {//该类,在signatureMap中有需要被拦截的方法才生成代理类return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}
- 构建签名映射表
- K:StatementHandler.class、Executor.class、ParameterHandler.class、ResultSetHandler.class
- V:update, query, flushStatements, commit, rollback…等
//Plugin类中 private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);if (interceptsAnnotation == null) {throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());}//1.提取@Intercepts注解中的签名数组Signature[] sigs = interceptsAnnotation.value();Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();for (Signature sig : sigs) {Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());try {//2.获取拦截的类的方法。该方法是最终被拦截的方法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;}
找出所有需要被代理的接口
//Plugin类中 private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {Set<Class<?>> 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<?>[0]); }
- 构建签名映射表
-
执行拦截
//Plugin是一个InvocationHandler public class Plugin implements InvocationHandler {private final Object target;private final Interceptor interceptor;private final Map<Class<?>, Set<Method>> signatureMap;private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {this.target = target;this.interceptor = interceptor;this.signatureMap = signatureMap;}//执行拦截@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {Set<Method> methods = signatureMap.get(method.getDeclaringClass());//1.找到被拦截的方法if (methods != null && methods.contains(method)) {//2.执行拦截逻辑//interceptor.intercept方法由用户实现return interceptor.intercept(new Invocation(target, method, args));}return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);}} }
-
小结
-
被拦截的类和方法
拦截的类 拦截的方法 Executor update, query, flushStatements, commit, rollback,getTransaction, close, isClosed ParameterHandler getParameterObject, setParameters StatementHandler prepare, parameterize, batch, update, query ResultSetHandler handleResultSets, handleOutputParameters -
代码执行链路
-
核心类图
Interceptor
:拦截器,定义了拦截逻辑,以及拦截器链的注册。InterceptorChain
:拦截器链,将多个拦截器组成拦截器链。Plugin
:Plugin 则是一种利用 Interceptor 实现的插件机制,它可以对指定的目标对象进行代理,并在方法调用前后插入额外的逻辑Invocation
:封装了被拦截方法的信息,包括被拦截方法的对象,被拦截方法的方法,被拦截方法的参数。- Plugin 是基于 Interceptor 实现的插件机制,而 Interceptor 是实现拦截器功能的接口。
-
自定义插件
-
MyBaits的Interceptor定义
public interface Interceptor {Object intercept(Invocation invocation) throws Throwable;default Object plugin(Object target) {return Plugin.wrap(target, this);}default void setProperties(Properties properties) {// NOP}}
intercept
方法:核心方法,最终会被调用,执行自定义的拦截逻辑。plugin
方法:⽣成target的代理对象setProperties
方法:传递插件所需参数,插件初始化的时候调⽤,也只调⽤⼀次
-
自定义Interceptor实现类
@Intercepts({//注意看这个⼤花括号,也就这说这⾥可以定义多个@Signature对多个地⽅拦截,都⽤这个拦截器@Signature(type = StatementHandler.class,//这是指拦截哪个接⼝method = "prepare",//这个接⼝内的哪个⽅法名,不要拼错了args = {Connection.class, Integer.class})// 这是拦截的⽅法的⼊参,按顺序写到这,不要多也不要少,如果⽅法重载,可是要通过⽅法名和⼊参来确定唯⼀的 }) public class MyPlugin implements Interceptor {/*拦截方法:只要被拦截的目标对象的目标方法被执行时,每次都会执行intercept方法*/@Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println("对方法进行了增强....");return invocation.proceed(); //原方法执行}/*** 包装⽬标对象 为⽬标对象创建代理对象,会将当前Interceptor也包装进去** @param target 要拦截的对象* @return 代理对象*/@Overridepublic Object plugin(Object target) {Object wrap = Plugin.wrap(target, this);return wrap;}/*获取配置文件的参数插件初始化的时候调⽤,也只调⽤⼀次,插件配置的属性从这⾥设置进来*/@Overridepublic void setProperties(Properties properties) {System.out.println("获取到的配置文件的参数是:" + properties);} }
-
sqlMapConfig.xml中配置插件,启用自定义的Interceptor实现类
<plugins><plugin interceptor="com.muchfish.plugin.MyPlugin"><property name="name" value="tom"/></plugin></plugins>
-
测试
public class PluginTest {@Testpublic void test() throws IOException {InputStream resourceAsStream =Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = newSqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();IUserDao userMapper = sqlSession.getMapper(IUserDao.class);List<User> byPaging = userMapper.findAllUserAndRole();for (User user : byPaging) {System.out.println(user);}} }
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插件
<plugins><!-- <plugin interceptor="com.muchfish.plugin.MyPlugin"><property name="name" value="tom"/></plugin>--><!--注意:分⻚助⼿的插件 配置在通⽤馆mapper之前*--><plugin interceptor="com.github.pagehelper.PageHelper"><property name="dialect" value="mysql"/></plugin></plugins>
-
测试分⻚数据获取
@Testpublic void testPageHelper() throws IOException {InputStream resourceAsStream =Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = newSqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();IUserDao userMapper = sqlSession.getMapper(IUserDao.class);//设置分⻚参数PageHelper.startPage(1, 2);List<User> select = userMapper.findAllUserAndRole();for (User user : select) {System.out.println(user);}}
通⽤ mapper
基于插件实现单表增删改查?
-
导⼊通⽤tk.mybatis的坐标
<!--通用mapper--> <dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId><version>3.1.2</version> </dependency>
-
在mybatis核⼼配置⽂件中配置tk.mybatis插件
<plugins><plugin interceptor="com.muchfish.plugin.MyPlugin"><property name="name" value="tom"/></plugin><!--注意:分⻚助⼿的插件 配置在通⽤馆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 = "user") public class UserDO {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer id;private String username;private String password;private String birthday;//...省略getter/setter }
-
定义Dao继承Mapper类
import com.muchfish.pojo.UserDO; import tk.mybatis.mapper.common.Mapper;public interface UserMapper extends Mapper<UserDO> { }
-
测试
@Testpublic void mapperTest() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);UserDO user = new UserDO();user.setId(1);UserDO user1 = mapper.selectOne(user);System.out.println(user1);//2.example方法Example example = new Example(User.class);example.createCriteria().andEqualTo("id",1);List<UserDO> users = mapper.selectByExample(example);for (UserDO user2 : users) {System.out.println(user2);}}
-
通用Mapper拦截器部分源码
/*** 通用Mapper拦截器** @author liuzh*/ @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) }) public class MapperInterceptor implements Interceptor {private final MapperHelper mapperHelper = new MapperHelper();@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object[] objects = invocation.getArgs();MappedStatement ms = (MappedStatement) objects[0];String msId = ms.getId();//不需要拦截的方法直接返回if (mapperHelper.isMapperMethod(msId)) {//第一次经过处理后,就不会是ProviderSqlSource了,一开始高并发时可能会执行多次,但不影响。以后就不会在执行了if (ms.getSqlSource() instanceof ProviderSqlSource) {mapperHelper.setSqlSource(ms);}}return invocation.proceed();}@Overridepublic Object plugin(Object target) {if (target instanceof Executor) {return Plugin.wrap(target, this);} else {return target;}}@Overridepublic void setProperties(Properties properties) {mapperHelper.setProperties(properties);} }
// MapperHelper类中/*** 注册的通用Mapper接口*/private Map<Class<?>, MapperTemplate> registerMapper = new ConcurrentHashMap<Class<?>, MapperTemplate>();/*** 判断当前的接口方法是否需要进行拦截** @param msId* @return*/public boolean isMapperMethod(String msId) {if (msIdSkip.get(msId) != null) {return msIdSkip.get(msId);}for (Map.Entry<Class<?>, MapperTemplate> entry : registerMapper.entrySet()) {if (entry.getValue().supportMethod(msId)) {msIdSkip.put(msId, true);return true;}}msIdSkip.put(msId, false);return false;}
// MapperHelper类中/*** 注册的通用Mapper接口*/private Map<Class<?>, MapperTemplate> registerMapper = new ConcurrentHashMap<Class<?>, MapperTemplate>();/*** 配置属性** @param properties*/ public void setProperties(Properties properties) {if (properties == null) {return;}String UUID = properties.getProperty("UUID");if (UUID != null && UUID.length() > 0) {setUUID(UUID);}//...省略//注册通用接口String mapper = properties.getProperty("mappers");if (mapper != null && mapper.length() > 0) {String[] mappers = mapper.split(",");for (String mapperClass : mappers) {if (mapperClass.length() > 0) {registerMapper(mapperClass);}}} }/*** 注册通用Mapper接口** @param mapperClass* @throws Exception*/public void registerMapper(Class<?> mapperClass) {if (!registerMapper.containsKey(mapperClass)) {registerMapper.put(mapperClass, fromMapperClass(mapperClass));}//自动注册继承的接口Class<?>[] interfaces = mapperClass.getInterfaces();if (interfaces != null && interfaces.length > 0) {for (Class<?> anInterface : interfaces) {registerMapper(anInterface);}}}
- 只增强了
Executor
- 在
MapperInterceptor.setProperties
进行了mapper注册 - 会在注册的mapper中进行匹配,判断是否对该
MappedStatementId
进行拦截
- 只增强了
相关文章:
MyBatis的基本应用
源码地址 01.MyBatis环境搭建 添加MyBatis的坐标 <!--mybatis坐标--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.9</version></dependency><!--mysql驱动坐…...
Day80:服务攻防-中间件安全HW2023-WPS分析WeblogicJettyJenkinsCVE
目录 中间件-Jetty-CVE&信息泄漏 CVE-2021-34429(信息泄露) CVE-2021-28169(信息泄露) 中间件-Jenkins-CVE&RCE执行 cve_2017_1000353 CVE-2018-1000861 cve_2019_1003000 中间件-Weblogic-CVE&反序列化&RCE 应用金山WPS-HW2023-RCE&复现&上线…...
使用generator实现async函数
我们先来看一下async函数是怎么使用的 const getData (sec) > new Promise((resolve) > {setTimeout(() > resolve(sec * 2), sec * 1000);})// aim to get this asycnFun by generator async function asyncFun() {const data1 await getData(1);const data2 awa…...
go并发请求url
sync.WaitGroup写法 package mainimport ("database/sql""fmt""net/http""sync""time"_ "github.com/go-sql-driver/mysql" )func main() {//开始计时start : time.Now()//链接数据库,用户名…...
刷题之Leetcode704题(超级详细)
704. 二分查找 力扣题目链接(opens new window)https://leetcode.cn/problems/binary-search/ 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标&am…...
leetcode热题100.前k个高频元素
作者:晓宜 🌈🌈🌈 个人简介:互联网大厂Java准入职,阿里云专家博主,csdn后端优质创作者,算法爱好者 ❤️❤️❤️ 你的关注是我前进的动力😊 Problem: 347. 前 K 个高频元…...
LangChain Demo | Agent X ReAct X wikipedia 询问《三体》的主要内容
背景 LangChain学习中,尝试改了一下哈里森和吴恩达课程当中的问题,看看gpt-3.5-turbo在集成了ReAct和wikipedia后,如何回答《三体》的主要内容是什么这个问题,当然,主要是为了回答这问题时LangChain内部发生了什么。所…...
Revit 2025新功能一览~
Hello大家好!我是九哥~ Revit2025已经更新,安装后,简单试了下,还是挺不错的,流畅度啊,新功能啊,看来还是有听取用户意见的,接下来就简单看看都有哪些新功能。 好了,今天的…...
Head First Design Patterns -代理模式
什么是代理模式 代理模式为另一个对象提供替身或者占位符,以便控制客户对对象的访问,管理访问的方式有很多种。例如远程代理、虚拟代理、保护代理等。 远程代理:管理客户和远程对象之间的交互。 虚拟代理:控制访问实例化开销大的对…...
第十三题:天干地支
题目描述 古代中国使用天干地支来记录当前的年份。 天干一共有十个,分别为:甲(jiǎ)、乙(yǐ)、丙(bǐng)、丁(dīng)、戊(w)、己&a…...
8000预算可以购买阿里云服务器配置整理
一个月8000元预算如何选择阿里云服务器配置?八千预算可选的阿里云服务器配置相当高了,这个预算可以购买阿里云企业级独享型云服务器,至少8核以上的配置,这个预算可以支持复杂、高负载或大规模的业务需求。阿里云服务器网整理8000元…...
游戏APP如何提高广告变现收益的同时,保证用户留存率?
APP广告变现对接第三方聚合广告平台主要通过SDK文档对接,一些媒体APP不具备专业运营广告变现的对接能力和资源沉淀,导致APP被封控,设置列入黑名单,借助第三方聚合广告平台进行商业化变现是最佳选择。#APP广告变现# 接入第三方平台…...
Linux ulimit命令教程:如何查看和设置系统资源限制(附实例详解和注意事项)
Linux ulimit命令介绍 ulimit是一个内置的Linux shell命令,它允许查看或限制单个用户可以消耗的系统资源量。在有多个用户和系统性能问题的环境中,限制资源使用是非常有价值的。 Linux ulimit命令适用的Linux版本 ulimit命令在所有主流的Linux发行版中…...
(delphi11最新学习资料) Object Pascal 学习笔记---第8章第5节(封闭类和Final方法)
8.5.2 封闭类和Final方法 如前所述,Java 采用非常动态的方法,默认情况下采用延迟绑定(或虚函数)。因此,Java 语言引入了一些概念,如不能继承的类(封闭类)和不能在派生类中覆盖的方法…...
vue3从精通到入门12:vue3的生命周期和组件
生命周期: 生命周期钩子主要包括: beforeCreate:组件实例被创建之前调用。在这个阶段,组件的 props 和 data 还未被初始化。created:组件实例创建完成后调用。在这个阶段,组件的 props 和 data 已经被初始…...
力扣热题100_链表_21_合并两个有序链表
文章目录 题目链接解题思路解题代码 题目链接 21. 合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1: 输入:l1 [1,2,4], l2 [1,3,4] 输出:[1,1,2,3,4,4] 示例…...
探索未来智慧酒店网项目接口架构
在数字化时代,智慧酒店已成为酒店业发展的重要趋势之一。智慧酒店网项目接口架构作为支撑智慧酒店运营的核心技术之一,其设计和优化对于提升用户体验、提高管理效率具有重要意义。本文将深入探讨智慧酒店网项目接口架构的设计理念和关键要素。 ### 智慧…...
os模块篇(十三)
文章目录 os.mknod(path, mode0o600, device0, *, dir_fdNone)os.major(device, /)os.minor(device, /)os.makedev(major, minor, /)os.pathconf(path, name)os.readlink(path, *, dir_fdNone)os.remove(path, *, dir_fdNone)os.removedirs(name)os.rename(src, dst, *, src_di…...
【JavaEE初阶系列】——文件操作 IO 之 文件系统操作
目录 📝认识文件 🚩树型结构组织 和 目录 🎈绝对路径和相对路径 🚩文件类型 📝文件系统操作 🎈File 概述 🎈File类的使用 1. 绝对路径 vs 相对路径 2. 路径分隔符 3. 静态成员变量 4…...
JAVA 学习·类与方法
不同于 C ,Java 是一门面向对象的编程语言。C 也有面向对象的内容,但是 C 和 Java 在方法的具体实现上存在区别。 方法的定义 方法(method)是为执行一个复杂操作组合在一起的语句集合。一个类中可以声明多个方法。其语法是采用 BNF 范式(Bac…...
4. python练习题4-水仙花数
4. python练习题4-水仙花数 【目录】 文章目录 4. python练习题4-水仙花数1. 目标任务2. 水仙花数的特点3. 如何判断一个数是否是水仙花数?4. 打印3位水仙花数5. 判断一个数是不是水仙花数6. 列表推导式6. 列表推导式判断一个数是不是水仙花数 【正文】 1. 目标任务…...
【Qt 学习笔记】Qt 开发环境的搭建 | Qt 安装教程
博客主页:Duck Bro 博客主页系列专栏:Qt 专栏关注博主,后期持续更新系列文章如果有错误感谢请大家批评指出,及时修改感谢大家点赞👍收藏⭐评论✍ Qt 开发环境的搭建 | Qt 安装教程 文章编号:Qt 学习笔记 /…...
ids工业相机与电控位移台同步控制及数据采集
通过VS2017和OpenCV,实现ids工业相机与电控位移台同步控制及数据采集 目录项目环境配置代码流程及思路项目架构项目开发运行效果开发关键ids相机配置位移台环境配置相机头文件相机参数设置保存图像函数设置电控位移台头文件电控位移台设置参数最后就是通过main函数进行调用和控…...
景联文科技提供高质量医疗健康AI大模型数据
医疗行业是典型的知识和技术密集型行业,其发展水平直接关系到国民健康和生命质量。 医疗健康AI大模型,作为人工智能的一个分支,能够通过学习大量的数据来生成新的数据实例,在医药研发、医学影像、医疗文本分析等都有广泛的应用前景…...
【Python第三方库】lxml 解析器和xpath路径语言
1.lxml是做什么的 是xml/html的解析器,主要是用来解析和提取html/xml数据 2.lxml语法 使用etree.HTML(html字符串),将字符串转换为Element对象通过使用Element对象.xpath(语法)提取信息,返回的是一个列表的内存地址,需要通过使用索引获取信…...
Java(Lambda、集合)、题解
一、Lambda表达式 标准格式 ()对应方法的形参 ;->固定格式 注意点: Lambda表达式可以用来简化匿名内部类的书写 Lambda表达式只能简化函数式接口的匿名内部类的写法 函数式接口: 有且仅有一个抽象方法的接口叫做函数式接口&…...
Transformer学习: Transformer小模块学习--位置编码,多头自注意力,掩码矩阵
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 Transformer学习 1 位置编码模块1.1 PE代码1.2 测试PE1.3 原文代码 2 多头自注意力模块2.1 多头自注意力代码2.2 测试多头注意力 3 未来序列掩码矩阵3.1 代码3.2 测试掩码 1 …...
easyexcel 动态列导出
1. 引入easyexcel <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.2.1</version></dependency> 2.导出write public void export(HttpServletResponse response) {try {String f…...
flink源码编译-job提交
1、启动standalone集群的taskmanager standalone集群中的taskmanager启动类为 TaskManagerRunner 2 打开master启动类 通过 ctrln快捷键,找到、并打开类: org.apache.flink.runtime.taskexecutor.TaskManagerRunner 3 修改运⾏配置 基本完全按照mas…...
Mysql密码修改问题
docker安装mysql,直接拉取镜像,挂载关键目录即可启动,默认3306端口。此时无法直接连接,需要配置密码。docker进入mysql容器中 docker exec -it mysql bash #mysq是容器名称,也可以用容器id通过修改mysql的配置进行免密…...
wordpress演示数据导入/新冠疫情最新消息
在开发中遇到了一个需要将Integer转Long的问题,才发现,包装类型是不能强制转换的。 基本类型: 一、将long型转化为int型long a 10; int b (int)a; 二、将int型转化为long型int a 10;long b (long)a;包装类型 三、将Integer型转化…...
葫芦岛建设信息网站/淘宝关键词搜索工具
废话不多说还是打开word-视图-工具栏-visual basic,选择Microsoft web浏览器。添加该控件到word页面中。界面设计如下点击按钮编写如下代码Sub CommandButton1_Click() Sub CommandButton1_Click() On Error GoTo errplay If TextBox1.Text "" Then MsgBox "网…...
php网站开发教案/山东关键词优化联系电话
PHY层(Physical layer物理层)。PHY层用来指定BLE所用的无线频段,调制解调方式和方法等。PHY层做得好不好,直接决定整个BLE芯片的功耗,灵敏度以及selectivity等射频指标。 LL层(Link Layer链路层࿰…...
table做的电脑端网站改成手机板/南宁优化网站网络服务
路由分为静态路由和动态路由,其相应的路由表称为静态路由表和动态路由表。静态路由表由网络管理员在系统安装时根据网络的配置情况预先设定,网络结构发生变化后由网络管理员手工修改路由表。动态路由随网络运行情况的变化而变化,路由器根据路…...
丽水品牌网站建设/北京网站推广机构
网络层向上只提供简单灵活的、无连接的、尽最大努力交付的数据报服务网络层不提供服务质量承诺虚电路服务和数据报服务对比4.IP协议配套的4个协议地址解析协议ARP逆地址解析协议RARP网际控制报文协议ICMP网际组管理协议IGMP5.四种不同的中间设备(1)物理层…...
上海电商网站开发/百度分析工具
原文:从头开始学JavaScript (二)——变量及其作用域一、变量 ECMAscript变量是松散型变量,所谓松散型变量,就是变量名称可以保存任何类型的数据,每个变量仅仅是一个用于保存值的占位符。 定义:var firstDemo; 二、变量的作用域 2.…...