MyBatis的基本应用

源码地址

01.MyBatis环境搭建

  1. 添加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>
    
  2. 创建数据表

  3. 编写DO实体类

  4. 编写映射⽂件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>
    
  5. 编写核⼼⽂件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
    
  6. 编写测试类

        @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

  1. CRUD的API

    • sqlSession.selectList()、sqlSession.selectOne()
    • sqlSession.insert()
    • sqlSession.update()
    • sqlSession.delete()
  2. 注意问题

    • 在进行增删改操作时,要手动提交事务。

      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 接⼝开发需要遵循以下规范:

  1. Mapper.xml⽂件中的namespace与mapper接⼝的全限定名相同
  2. Mapper接⼝⽅法名和Mapper.xml中定义的每个statement的id相同
  3. Mapper接⼝⽅法的输⼊参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
  4. Mapper接⼝⽅法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

在这里插入图片描述

05.MyBatis的多对多复杂映射

  1. DO类

    public class User {private Integer id;private String username;private String password;private String birthday;//表示用户关联的角色private List<Role> roleList = new ArrayList<>();//。。。省略getter/setter
    }    
    
  2. 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>
    
  3. 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语句

在这里插入图片描述

  • 总结
    1. 第⼀次发起查询⽤户id为1的⽤户信息,先去找缓存中是否有id为1的⽤户信息,如果没有,从 数据库查询⽤户信息。得到⽤户信息,将⽤户信息存储到⼀级缓存中。
    2. 如果中间sqlSession去执⾏commit操作(执⾏插⼊、更新、删除),则会清空SqlSession中的 ⼀级缓存,这样做的⽬的为了让缓存中存储的是最新的信息,避免脏读。
    3. 第⼆次发起查询⽤户id为1的⽤户信息,先去找缓存中是否有id为1的⽤户信息,缓存中有,直 接从缓存中获取⽤户信息

二级缓存

在这里插入图片描述

  1. 使用二级缓存

    1. 开启⼆级缓存

      1. 在全局配置⽂件sqlMapConfig.xml⽂件中开启

        <!--开启⼆级缓存 注意<settings>标签的顺序,在<properties>标签后面-->
        <settings>
        <setting name="cacheEnabled" value="true"/>
        </settings>
        
      2. 在Mapper.xml⽂件中开启缓存

            <!--使用二级缓存--><cache></cache>
        
      3. 在DAO接口中开启缓存(可选。使用@CacheNamespace会报错。)

        @CacheNamespace
        public interface IUserDao {}
        

        @CacheNamespaceRef(IUserDao.class) 
        //@CacheNamespace
        public interface IUserDao {}
        
        • xml和对应dao接口上同时开启二级缓存会报错,此时只能使用@CacheNamespaceRef
    2. DO序列化

      public class User implements Serializable {//。。。
      }
      
    3. 测试

       @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();}
      
  2. useCache和flushCache

    1. useCache:开启或禁用缓存

          <select id="findById" resultType="com.muchfish.pojo.User" useCache="true">select * from user where id = #{id}</select>
      
    2. flushCache:刷新缓存

          <select id="findById" resultType="com.muchfish.pojo.User" useCache="true" flushCache="true">select * from user where id = #{id}</select>
      

二级缓存整合redis

  1. pom⽂件

            <dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-redis</artifactId><version>1.0.0-beta2</version></dependency>
    
  2. 配置⽂件

    <?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" />
    
  3. redis.properties

    redis.host=localhost
    redis.port=6379
    redis.connectionTimeout=5000
    redis.password=
    redis.database=0
    
  4. 测试

        @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();}
  5. 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);}}}}}}

    在这里插入图片描述

  6. 小结

    1. 二级缓存使用redis可以实现分布式缓存
    2. 自定义实现二级缓存,通过实现Cache接口
    3. RedisCache通过RedisConfigurationBuilder加载redis.properties中的配置
    4. RedisCache使用了JedisPool
    5. RedisCache使用了模板方法,完成putObjectgetObjectremoveObjectclear等操作

08.MyBatis插件

Mybatis插件原理

  1. 注册插件

    //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注入的方式注册拦截器的方式代码不在此处
  2. 应用插件

    //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}}
    
  3. 生成插件拦截代理类

    //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]);
    }
    
  4. 执行拦截

    //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);}}
    }  
    
  5. 小结

    1. 被拦截的类和方法

      拦截的类拦截的方法
      Executorupdate, query, flushStatements, commit, rollback,getTransaction, close, isClosed
      ParameterHandlergetParameterObject, setParameters
      StatementHandlerprepare, parameterize, batch, update, query
      ResultSetHandlerhandleResultSets, handleOutputParameters
    2. 代码执行链路

      parseConfiguration-解析SqlMapConfig.xml
      pluginElement-解析插件
      configuration.addInterceptor-注册拦截器
      interceptorChain.addInterceptor-加入拦截器列表
      InterceptorChain.pluginAll-应用全部插件
      Interceptor.plugin-组装递归式插件链
      Plugin.wrap-组装单个插件
      interceptorChain.pluginAll-拦截parameterHandler
      interceptorChain.pluginAll-拦截resultSetHandler
      interceptorChain.pluginAll-拦截statementHandler
      interceptorChain.pluginAll-拦截executor
      getSignatureMap-构建签名映射表
      getAllInterfaces-需要被代理的接口
      Proxy.newProxyInstance-创建代理
      interceptsAnnotation.value-提取Intercepts注解中的签名数组
      getMethod-获取拦截的类的方法.该方法是最终被拦截的方法
      Plugin.invoke-触发代理类拦截
      interceptor.intercept-用户自定义的插件拦截器方法
    3. 核心类图

      在这里插入图片描述

      • Interceptor:拦截器,定义了拦截逻辑,以及拦截器链的注册。
      • InterceptorChain:拦截器链,将多个拦截器组成拦截器链。
      • Plugin:Plugin 则是一种利用 Interceptor 实现的插件机制,它可以对指定的目标对象进行代理,并在方法调用前后插入额外的逻辑
      • Invocation:封装了被拦截方法的信息,包括被拦截方法的对象,被拦截方法的方法,被拦截方法的参数。
      • Plugin 是基于 Interceptor 实现的插件机制,而 Interceptor 是实现拦截器功能的接口。

自定义插件

  1. 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方法:传递插件所需参数,插件初始化的时候调⽤,也只调⽤⼀次
  2. 自定义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);}
    }
    
  3. sqlMapConfig.xml中配置插件,启用自定义的Interceptor实现类

        <plugins><plugin interceptor="com.muchfish.plugin.MyPlugin"><property name="name" value="tom"/></plugin></plugins>
    
  4. 测试

    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分⻚插件

  1. 导⼊通⽤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>
    
  2. 在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>
    
  3. 测试分⻚数据获取

        @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

基于插件实现单表增删改查?

  1. 导⼊通⽤tk.mybatis的坐标

    <!--通用mapper-->
    <dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId><version>3.1.2</version>
    </dependency>
    
  2. 在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>
    
  3. 实体类设置主键

    @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
    }    
    
  4. 定义Dao继承Mapper类

    import com.muchfish.pojo.UserDO;
    import tk.mybatis.mapper.common.Mapper;public interface UserMapper extends Mapper<UserDO> {
    }
    
  5. 测试

        @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);}}
    
  6. 通用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进行拦截

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/296564.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】联合和枚举

个人主页点这里~ 联合和枚举 一、联合体1、联合体类型的声明2、联合体成员的特点3、与结构体对比4、计算联合体大小 二、枚举1、枚举的声明2、枚举的优点3、枚举类型的使用 一、联合体 1、联合体类型的声明 联合体的定义与结构体相似&#xff0c;但是联合体往往会节省更多的空…

【科研笔记】知识星球不可选择内容爬虫

知识星球不可选择内容爬虫 1 背景2 实现3 拓展遗留问题1 背景 针对与知识星球中,电脑打开网页不可选择复制粘贴的问题,进行爬虫处理,获取网页的内容,并保存在本地 2 实现 需要下载python,和爬虫的第三方库selenium,可以查看博客中有关selenium的内容进行回顾。当前使用…

Compose 中状重组

一、状态变化 1.1 状态变化是什么 根据上篇文章的讲解&#xff0c;在 Compose 我们使用 State 来声明一个状态&#xff0c;当状态发生变化时&#xff0c;则会触发重组。那么状态变化是指什么呢&#xff1f; 下面我们来看一个例子&#xff1a; Composable fun NumList() {val…

非比较排序之计数排序

思想&#xff1a; 比较排序又称为鸽巢原理&#xff0c;是对哈希直接定址法的变形应用。 思想步骤&#xff1a; 统计相同元素出现的次数根据统计的结果将序列收回到原来的序列中 具体步骤&#xff1a; 先统计数据的大小范围&#xff0c;开辟一个大小为范围的数组( 最大值 -…

世优科技上榜2024年度《中国虚拟数字人影响力指数报告》

日前&#xff0c;第三期《中国虚拟数字人影响力指数报告》在中国网络视听大会上正式发布。本期《报告》由中国传媒大学媒体融合与传播国家重点实验室&#xff08;以下简称“国重实验室”&#xff09;、中国传媒大学数字人研究院编制&#xff0c;中国网络视听协会、人民日报智慧…

代码随想录第29天|491.递增子序列 46.全排列 47.全排列 II

目录&#xff1a; 491.递增子序列 46.全排列 47.全排列 II 491.递增子序列 491. 非递减子序列 - 力扣&#xff08;LeetCode&#xff09; 代码随想录 (programmercarl.com) 回溯算法精讲&#xff0c;树层去重与树枝去重 | LeetCode&#xff1a;491.递增子序列_哔哩哔哩_bili…

字符分类函数

字符分类函数 C语言中有⼀系列的函数是专门做字符分类的&#xff0c;也就是⼀个字符是属于什么类型的字符的。这些函数的使用都需要包含⼀个头文件是 ctype.h 这些函数的使用方法非常类似&#xff0c;我们就讲解⼀个函数的事情&#xff0c;其他的非常类似&#xff1a; int i…

绩效考核存在合理性、公平性、客观性吗?

目录 一、绩效考核流于形式&#xff1a;没有实际考核过 二、考核结果的确定: 主管一人说了算 三、考核结果&#xff1a; 与绩效奖金挂钩吗&#xff1f; 四、考核的滥用&#xff1a;成为公司排挤迫使员工离职的手段 五、公司说&#xff1a; 让你滚蛋&#xff0c;谁还会发你奖…

使用 BeeWare 构建 Python GUI 应用程序

点击下方卡片&#xff0c;关注“小白玩转Python”公众号 本文探讨使用 BeeWare 套件通过 Python 构建应用程序的基础知识&#xff0c;详细介绍其功能、优点以及与其他流行框架的比较。 由于 Python 语言的简单性和多功能性&#xff0c;用它构建应用程序变得越来越流行。在 Pyth…

【项目新功能开发篇】开发编码

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

图的应用解析

01&#xff0e;任何一个无向连通图的最小生成树(B )。 A.有一棵或多棵 B.只有一棵 C.一定有多棵 D.可能不存在 02.用Prim算法和Kruskal算法构造图的最小生成树&#xff0c…

使用ffmpeg将视频解码为帧时,图像质量很差

当使用ffmpeg库自带的ffmpeg.exe对对视频进行解帧或合并时&#xff0c;结果质量很差。导致这种原因的是在使用ffmpeg.exe指令进行解帧或合并时使用的是默认的视频码率&#xff1a;200kb/s。 如解帧指令&#xff1a; ffmpeg.exe -i 600600pixels.avi -r 2 -f image2 img/%03d.…

typdef:深入理解C语言中typdef关键词的用法

typedef&#xff1a;C语言中的类型重命名关键词 在C语言中&#xff0c;typedef 是一个非常有用的关键词&#xff0c;它允许我们为现有的数据类型定义一个新的名称。这不仅使得代码更加清晰易读&#xff0c;还提高了代码的可维护性。在这篇博客中&#xff0c;我们将深入探讨 ty…

Native Instruments Kontakt 7 for Mac v7.9.0 专业音频采样

Native Instruments Kontakt 7是一款强大的软件采样器&#xff0c;它允许用户从各种来源采样音频并进行编辑和处理。它包含大量预设采样库&#xff0c;包括乐器、合成器、鼓组和声音效果等。此外&#xff0c;Kontakt 7还允许用户创建自己的采样库&#xff0c;以便根据自己的需要…

vue2源码解析——vue中如何进行依赖收集、响应式原理

vue每个组件实例vm都有一个渲染watcher。每个响应式对象的属性key都有一个dep对象。所谓的依赖收集&#xff0c;就是让每个属性记住它依赖的watcher。但是属性可能用在多个模板里&#xff0c;所以&#xff0c;一个属性可能对应多个watcher。因此&#xff0c;在vue2中&#xff0…

NineData云原生智能数据管理平台新功能发布|2024年3月版

数据库 DevOps - 大功能升级 SQL 开发早期主要提供 SQL 窗口&#xff08;IDE&#xff09;功能&#xff0c;在产品经过将近两年时间的打磨&#xff0c;新增了大量的企业级功能&#xff0c;已经服务了上万开发者&#xff0c;覆盖了数据库设计、开发、测试、变更等生命周期的功能…

python的pip如何升级

升级pip的方法如下&#xff1a; 打开命令行工具。在Windows系统中&#xff0c;可以通过按下WinR键&#xff0c;然后输入"cmd"来打开命令提示符&#xff1b;在Mac或Linux系统中&#xff0c;可以直接打开终端。检查当前pip版本。在终端或命令行中输入以下命令&#…

《C Prime Plus》02

1. UNIX 系统 C语言因UNIX系统而生&#xff0c;也因此而流行&#xff0c;所以我们从UNIX系统开始&#xff08;注意&#xff1a;我们提到的UNIX还包含其他系统&#xff0c;如FreeBSD&#xff0c;它是UNIX的一个分支&#xff0c;但是由于法律原因不使用该名称&#xff09;。 UN…

蓝桥杯备考

目录 P8823 [传智杯 #3 初赛] 期末考试成绩 题目描述 输入格式 输出格式 输入输出样例 说明/提示 代码 P8828 [传智杯 #3 练习赛] 直角三角形 题目描述 输入格式 输出格式 输入输出样例 代码 P8833 [传智杯 #3 决赛] 课程 题目背景 题目描述 输入格式 输出格式…

并发编程之线程池的应用以及一些小细节的详细解析

线程池在实际中的使用 实际开发中&#xff0c;最常用主要还是利用ThreadPoolExecutor自定义线程池&#xff0c;可以给出一些关键的参数来自定义。 在下面的代码中可以看到&#xff0c;该线程池的最大并行线程数是5&#xff0c;线程等候区&#xff08;阻塞队列)是3&#xff0c;即…