Mybatis进阶功能介绍

前言

MyBatis 本是apache的一个开源项目iBatis,而我们对于mybatis上的使用包括最基本的使用 CRUD上的使用,而本篇文章会除了会介绍CRUD的使用、以及集成redis,以及 如何开启多级缓存, 多数据源,以及Mybatis插件实现原理 分页插件等等 这都是在根据项目的大小以及根据业务功能上的支撑 是否需要达到的情况,对于mybatis 在不同方向上的扩展。

Mybatis简介

官方文档:mybatis – MyBatis 3 | Introduction

中文网:mybatis – MyBatis 3 | 简介

github:https://github.com/mybatis/

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
一个半自动化的 ORM 框架( Object Relation Mapping) 。与其他 ORM 框架对比,它对 SQL 的控制更加灵活,可以写复杂的SQL 语句。 Hibernate JPA 无法灵活方便的做到 SQL 灵活控制,特别是动态 SQL 语句。

在使用上,突出一点是对sql更加灵活  可以写复杂的sql,当然也有非常多的灵活控件,这也是mybatis为什么这么火的原因把。

Mybatis整体框架

 mybatis为什么能将我们写的sql交给驱动发送到 mysql 、oracle等等数据库上进行执行。通过  sqlsessionfactory 创建 sqlsession    交给执行器执行  具体的sql    映射到  mappedstatement

整个框架支撑着。

SpringBoot项目如何使用

1. 通过SpringBoot创建Maven工程
<dependency>
   			 <groupId>org.mybatis.spring.boot</groupId>
    		 <artifactId>mybatis-spring-boot-starter</artifactId>
   			 <version>1.3.0</version>
		</dependency>

		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
		</dependency>

2. 准备数据库表、创建数据库表实体类
表可以建一张表即可
public class User {

	private String id;
	
	private String userName;
	
	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	@Override
	public String toString() {
		return "User [id=" + id + ", userName=" + userName + "]";
	}
	
	
}
3. 准备Mapper.xml
增删改查语句、传参、结果映射、命名空间、缓存
字符串替换: ${} ORDER BY ${columnName} 参数传递: #{} #{middleInitial,jdbcType=VARCHAR}
动态 SQL if/choose/when/otherwise/where/foreach

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

mybatis – MyBatis 3 | 动态 SQL

可以直接复制即可。

<mapper namespace="com.mybatis.dao.UserMapper">
	
	<select id="query"  resultType="com.mybatis.domain.User">
	    select id ,user_name from user where 1=1
	    <if test="userName != null">
	      and user_name like CONCAT('%',#{userName},'%')
	    </if>
	</select>
	
	<!-- <select id="findById"  resultType="User">
	    select id ,user_name 
	    from user 
	    where 1=1
	    <if test="userName != null">
	      and id = #{id}
	    </if>
	</select>
	
	<insert id="insert" parameterType="User">
		insert user(id, user_name) values(#{id}, #{userName})
	</insert> -->
</mapper>
4. 配置mybatis-config.xml 或者 在springboot 的 application.yml中

  • 数据源配置
采用 spring 方式,数据源配置交给 spring
原始方式则需要配置数据源 
<?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>
	<!--mybatis 系统配置 会改变 MyBatis 的运行时行为-->
	<settings>
	    <!-- 使全局的映射器启用或禁用缓存。 -->
		<setting name="cacheEnabled" value="true" />
		<!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 -->
		<setting name="lazyLoadingEnabled" value="true" />
		<!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 -->        
         <setting name="aggressiveLazyLoading" value="true"/>        
         <!-- 是否允许单条sql 返回多个数据集  (取决于驱动的兼容性) default:true -->
		<setting name="multipleResultSetsEnabled" value="true" />
		<!-- 是否可以使用列的别名 (取决于驱动的兼容性) default:true -->
		<setting name="useColumnLabel" value="true" />
		<!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。  default:false  -->
		<setting name="useGeneratedKeys" value="false" />
		<!-- 指定 MyBatis 如何自动映射 数据基表的列 NONE:不隐射 PARTIAL:部分  FULL:全部  -->
		<setting name="autoMappingBehavior" value="PARTIAL" />
		<!-- 这是默认的执行类型  (SIMPLE: 简单; REUSE: 执行器可能重复使用prepared statements语句;BATCH: 执行器可以重复执行语句和批量更新)  -->
		<setting name="defaultExecutorType" value="SIMPLE" />
		
		<setting name="defaultStatementTimeout" value="25" />
		
		<setting name="defaultFetchSize" value="100" />
		
		<setting name="safeRowBoundsEnabled" value="false" />
		<!-- 使用驼峰命名法转换字段。 -->
		<setting name="mapUnderscoreToCamelCase" value="true" />
		<!-- 设置本地缓存范围 session:就会有数据的共享  statement:语句范围 (这样就不会有数据的共享 ) defalut:session -->
		<setting name="localCacheScope" value="SESSION" />
		<!-- 默认为OTHER,为了解决oracle插入null报错的问题要设置为NULL -->
		<setting name="jdbcTypeForNull" value="NULL" />
		<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" />
	</settings>
	<!--
	<plugins>
		插件配置
		<plugin interceptor=""></plugin>
	</plugins>
	<mappers>
		mapper.xml的映射文件配置
		<package name=""/>
		<mapper url=""/>
	</mappers>
	<databaseIdProvider type=""></databaseIdProvider>
	<typeAliases>
		为 Java 类型设置一个缩写名字	
		<typeAlias alias="Author" type="domain.blog.Author"/>
		<package name="domain.blog"/>
	</typeAliases>
	<typeHandlers>
		查询结果类型转换器
		<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
	</typeHandlers>
	<databaseIdProvider type="DB_VENDOR">
		根据不同的数据库厂商执行不同的语句->
		<property name="SQL Server" value="sqlserver"/>
		<property name="DB2" value="db2"/>
		<property name="Oracle" value="oracle" />
	</databaseIdProvider>
	-->
</configuration>


mybatis – MyBatis 3 | 配置

  • 扫描包配置

  • 插件配置

mybatis的配置 在 application.yml  可以 指定   

mybatis:
     mapperLocations: classpath:/com/mybatis/dao/*.xml
     typeAliasesPackage: com.mybatis.dao  
     mapperScanPackage: com.mybatis.dao
     configLocation: classpath:/mybatis-config.xml
以及 mysql datasource的配置
mysql:
    datasource:

 url  以及  username   password  等

当然也可以采用 在  类上写就可以扫描路径就行。

 

5. 注解方式配置
CRUD 注解
动态 SQL
   直接在 方法上写 标签也可以的。
//注:方法名和要UserMapper.xml中的id一致
	@Update({"<script>", "update t_user " +
			"<set> " +
			"<if test='userName!=null'> user_name = #{userNaem}</if>" +
			"<if test='userName!=null'> user_name = #{userNaem}</if>" +
			"<if test='userName!=null'> user_name = #{userNaem}</if>" +
			"<if test='userName!=null'> user_name = #{userNaem}</if>" +
			"<if test='userName!=null'> user_name = #{userNaem}</if>" +
			"</set>" +
			"where id =#{id}",
			"</script>"})
	List<User> query(@Param("userName")String userName);
	
	@Select("select id,user_name from user where id=#{id} ")
	User findById(@Param("id")String id);
6. 测试类
7. Springboot中集成使用

在SpringBoot使用mybatis是非常简单的。

Mybatis集成Redis缓存

mybatis – MyBatis 3 | XML 映射器

mybatis-redis – MyBatis Redis | Reference Documentation

官方给我们提供的是,它支持缓存

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache/>

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

<!-- -->
 	<cache  />
	<!--
	<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
	-->
	<!-- 开启基于redis的二级缓存 -->
	<cache type="com.mybatis.cache.RedisCache"/>

其实对于 mybatis来说 ,它提供的cache接口 给我们,  具体的的实现由我们选择。

实现步骤:
1. 集成 Redis
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

在springboot中添加 redis的配置

spring:
  redis:
    # redis服务器地址(默认为localhost)
    host: localhost
    # redis端口(默认为6379)
    port: 6379
    database: 10
2. 实现 mybatis Cache 接口,将 Redis 作为二级缓存
String getId() mybatis 缓存操作对象的标识符。一个 mapper 对应一个 mybatis 的缓存操作对象。
void putObject(Object key, Object value) :将查询结果塞入缓存。
Object getObject(Object key) :从缓存中获取被缓存的查询结果。
Object removeObject(Object key) :从缓存中删除对应的 key value 。只有在回滚时触发。一般
我们也可以不用实现,具体使用方式请参考:
org.apache.ibatis.cache.decorators.TransactionalCache
void clear() :发生更新时,清除缓存。
int getSize() :可选实现。返回缓存的数量。
ReadWriteLock getReadWriteLock() :可选实现。用于实现原子性的缓存操作。 接下来,我们新
RedisCache 类,实现 Cache 接口:

cache 实现  必须要 有一个 构造参数 将 cache instance id  

mybatis 缓存操作对象的标识符。一个 mapper 对应一个 mybatis 的缓存操作对象。

 

 

 */
public class RedisCache implements Cache {
    private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final String id; // cache instance id
    private RedisTemplate redisTemplate;

    private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间

    public RedisCache(String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    /**
     * Put query result to redis
     *
     * @param key
     * @param value
     */
    @Override
    @SuppressWarnings("unchecked")
    public void putObject(Object key, Object value) {
        try {
            RedisTemplate redisTemplate = getRedisTemplate();
            ValueOperations opsForValue = redisTemplate.opsForValue();
            opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
            logger.debug("Put query result to redis");
        }
        catch (Throwable t) {
            logger.error("Redis put failed", t);
        }
    }

    /**
     * Get cached query result from redis
     *
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        try {
            RedisTemplate redisTemplate = getRedisTemplate();
            ValueOperations opsForValue = redisTemplate.opsForValue();
            logger.debug("Get cached query result from redis");
            return opsForValue.get(key);
        }
        catch (Throwable t) {
            logger.error("Redis get failed, fail over to db", t);
            return null;
        }
    }

    /**
     * Remove cached query result from redis
     *
     * @param key
     * @return
     */
    @Override
    @SuppressWarnings("unchecked")
    public Object removeObject(Object key) {
        try {
            RedisTemplate redisTemplate = getRedisTemplate();
            redisTemplate.delete(key);
            logger.debug("Remove cached query result from redis");
        }
        catch (Throwable t) {
            logger.error("Redis remove failed", t);
        }
        return null;
    }

    /**
     * Clears this cache instance
     */
    @Override
    public void clear() {
        RedisTemplate redisTemplate = getRedisTemplate();
        redisTemplate.execute((RedisCallback) connection -> {
            connection.flushDb();
            return null;
        });
        logger.debug("Clear all the cached query result from redis");
    }

    /**
     * This method is not used
     *
     * @return
     */
    @Override
    public int getSize() {
        return 0;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }

    private RedisTemplate getRedisTemplate() {
        if (redisTemplate == null) {
            redisTemplate = (RedisTemplate) SpringContextUtil.getBean("redisTemplate");
        }
        return redisTemplate;
    }
}

其实这个也可以抽象出来  成为 starter  成为 一个 公共的组件,更方便我们使用的。 

这里 是 mybatis去加载的 所以 没把这个类注册到 ioc容器中  ,而是采用 SpringContextUtil 方式去获取的。

    private RedisTemplate getRedisTemplate() {
        if (redisTemplate == null) {
            redisTemplate = (RedisTemplate) SpringContextUtil.getBean("redisTemplate");
        }
        return redisTemplate;
    }
自己实现的二级缓存,必须要有一个带 id 的构造函数,否则会报错。 我们使用 Spring 封装的
redisTemplate 来操作 Redis 。网上所有介绍 redis 做二级缓存的文章都是直接用 jedis 库,但是笔者
认为这样不够 Spring Style ,而且, redisTemplate 封装了底层的实现,未来如果我们不用 jedis
了,我们可以直接更换底层的库,而不用修改上层的代码。更方便的是,使用 redisTemplate ,我
们不用关心 redis 连接的释放问题,否则新手很容易忘记释放连接而导致应用卡死。
需要注意的是,这里不能通过 autowire 的方式引用 redisTemplate ,因为 RedisCache 并不是 Spring
容器里的 bean 。所以我们需要手动地去调用容器的 getBean 方法来拿到这个 bean ,具体的实现方
式请参考 Github 中的代码。
我们采用的 redis 序列化方式是默认的 jdk 序列化。所以数据库的查询对象(比如 Product 类)需要
实现 Serializable 接口。
这样,我们就实现了一个优雅的、科学的并且具有 Spring Style Redis 缓存类。

3. 开启二级缓存
Mapper.xml中开启二级缓存:

如果没有实现cache的情况 则采用默认的二级缓存。

<cache type="com.mybatis.cache.RedisCache"/>
<cache type="com.mybatis.cache.RedisCache"/> 表示开启基于 redis 的二级缓存,
并且在 update 语句中,我们设置 flushCache true ,这样在更新 product 信息时,能够自动失效缓
存(本质上调用的是 clear 方法)。
多数据源

在 application.yml中配置的

mysql:
    datasource:
        readSize: 1  # 读库个数
        type: com.alibaba.druid.pool.DruidDataSource
        mapperLocations: classpath:/com/mybatis/dao/*.xml
        configLocation: classpath:/mybatis-config.xml
        write:
           url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
           username: root
           password: 98.7%root
           driver-class-name: com.mysql.cj.jdbc.Driver
           minIdle: 5
           maxActive: 100
           initialSize: 10
           maxWait: 60000
           timeBetweenEvictionRunsMillis: 60000
           minEvictableIdleTimeMillis: 300000
           validationQuery: select 'x'
           testWhileIdle: true
           testOnBorrow: false
           testOnReturn: false
           poolPreparedStatements: true
           maxPoolPreparedStatementPerConnectionSize: 50
           removeAbandoned: true
           filters: stat
        read01:
           url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
           username: root
           password: 98.7%root
           driver-class-name: com.mysql.cj.jdbc.Driver
           minIdle: 5
           maxActive: 100
           initialSize: 10
           maxWait: 60000
           timeBetweenEvictionRunsMillis: 60000
           minEvictableIdleTimeMillis: 300000
           validationQuery: select 'x'
           testWhileIdle: true
           testOnBorrow: false
           testOnReturn: false
           poolPreparedStatements: true
           maxPoolPreparedStatementPerConnectionSize: 50
           removeAbandoned: true
           filters: stat

怎么实现多数据源 读写分离 的进行路由的。

sqlsessionfactory  只能修改 mybatis中创建。

根据数据类型进行判断 DataSourceConfiguration  去判断 

/**
 * 数据库源配置
 * 
 *
 */
@Configuration
public class DataSourceConfiguration {

	private static Logger log = LoggerFactory.getLogger(DataSourceConfiguration.class);
	
	@Value("${mysql.datasource.type}")
	private Class<? extends DataSource> dataSourceType;
    
	/**
	 * 写库 数据源配置
	 * @return
	 */
	@Bean(name = "writeDataSource")
    @Primary
    @ConfigurationProperties(prefix = "mysql.datasource.write")
    public DataSource writeDataSource() {
        log.info("-------------------- writeDataSource init ---------------------");
        return DataSourceBuilder.create().type(dataSourceType).build();
    }
	
	/**
     * 有多少个从库就要配置多少个
     * @return
     */
    @Bean(name = "readDataSource01")
    @ConfigurationProperties(prefix = "mysql.datasource.read01")
    public DataSource readDataSourceOne() {
        log.info("-------------------- read01 DataSourceOne init ---------------------");
        return DataSourceBuilder.create().type(dataSourceType).build();
    }
    
}

这个datasourcetype 则需要根据 选择 到那个 数据源。

添加MybatisConfiguration  并且 添加注解扫描。

@Configuration
@AutoConfigureAfter(DataSourceConfiguration.class)
@MapperScan(basePackages="com.mybatis.dao")

针对sqlsessionfactory进行代理 开创建sqlSessionFactorys

SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
            sessionFactoryBean.setDataSource(roundRobinDataSouceProxy());
  • 读取配置 
  • 设置mapper.xml文件所在位置 
  • 添加分页插件、打印sql插件

其实就是 创建 sqlsessionfacotry时创建动态代理对象,使用时,在判断使用那个 代理对象。

@Configuration
@AutoConfigureAfter(DataSourceConfiguration.class)
@MapperScan(basePackages="com.dongnaoedu.mybatis.dao")
public class MybatisConfiguration {

	private static Logger log = LoggerFactory.getLogger(MybatisConfiguration.class);
	
	@Value("${mysql.datasource.readSize}")
    private String readDataSourceSize;

	//XxxMapper.xml文件所在路径
	  @Value("${mysql.datasource.mapperLocations}")
      private String mapperLocations;

     //  加载全局的配置文件
      @Value("${mysql.datasource.configLocation}")
      private String configLocation;
      
	@Autowired
	@Qualifier("writeDataSource")
	private DataSource writeDataSource;
	@Autowired
	@Qualifier("readDataSource01")
	private DataSource readDataSource01;
	
	
    @Bean(name="sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactorys() throws Exception {
        log.info("--------------------  sqlSessionFactory init ---------------------");
        try {
            SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
            sessionFactoryBean.setDataSource(roundRobinDataSouceProxy());
            
            // 读取配置 
            sessionFactoryBean.setTypeAliasesPackage("com.mybatis.domain");
            
            //设置mapper.xml文件所在位置 
            Resource[] resources = new PathMatchingResourcePatternResolver().getResources(mapperLocations);
            sessionFactoryBean.setMapperLocations(resources);
            //设置mybatis-config.xml配置文件位置
            sessionFactoryBean.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));

            //添加分页插件、打印sql插件
            Interceptor[] plugins = new Interceptor[]{pageHelper(), new SqlPrintInterceptor()};
            sessionFactoryBean.setPlugins(plugins);
            
            return sessionFactoryBean.getObject();
        } catch (IOException e) {
            log.error("mybatis resolver mapper*xml is error",e);
            return null;
        } catch (Exception e) {
            log.error("mybatis sqlSessionFactoryBean create error",e);
            return null;
        }
    }
    
    @Bean("roundRobinDataSouceProxy")
    public AbstractRoutingDataSource roundRobinDataSouceProxy() {
    	
    	AbstractRoutingDataSource proxy = new AbstractRoutingDataSource() {
    		private AtomicInteger count = new AtomicInteger();
			@Override
			protected Object determineCurrentLookupKey() {
				String typeKey = DataSourceContextHolder.getReadOrWrite();
				
				if(typeKey == null) {
                    throw new NullPointerException("数据库路由是,决定使用哪个数据源。");
                }
				if(DataSourceType.write.getType().contentEquals(typeKey)) {
					return DataSourceType.write.getType();
				}
				
				int num = count.getAndAdd(1);
				int lookupKey = num % Integer.valueOf(readDataSourceSize);
				System.out.println("使用数据库Read-"+(lookupKey+1));
				return DataSourceType.read.getType()+(lookupKey+1);
			}
		};
		
		Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        //把所有数据库都放在targetDataSources中,注意key值要和determineCurrentLookupKey()中代码写的一至,
        //否则切换数据源时找不到正确的数据源
        targetDataSources.put(DataSourceType.write.getType(), writeDataSource);
        targetDataSources.put(DataSourceType.read.getType()+"1", readDataSource01);
		
		proxy.setDefaultTargetDataSource(writeDataSource);
		proxy.setTargetDataSources(targetDataSources);
		return proxy;
    }

    /**
     * 分页插件
     * @return
     */
    @Bean
    public PageHelper pageHelper() {
        PageHelper pageHelper = new PageHelper();
        Properties p = new Properties();
        p.setProperty("offsetAsPageNum", "true");
        p.setProperty("rowBoundsWithCount", "true");
        p.setProperty("reasonable", "true");
        p.setProperty("returnPageInfo", "check");
        p.setProperty("params", "count=countSql");
        pageHelper.setProperties(p);
        return pageHelper;
    }


    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    	return new SqlSessionTemplate(sqlSessionFactory);
    }
    
    //事务管理
    @Bean
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DataSourceTransactionManager((DataSource)SpringContextUtil.getBean("roundRobinDataSouceProxy"));
    }
    
}

选择那个 读写的数据源。

    @Bean("roundRobinDataSouceProxy")
    public AbstractRoutingDataSource roundRobinDataSouceProxy() {
    	
    	AbstractRoutingDataSource proxy = new AbstractRoutingDataSource() {
    		private AtomicInteger count = new AtomicInteger();
			@Override
			protected Object determineCurrentLookupKey() {
				String typeKey = DataSourceContextHolder.getReadOrWrite();
				
				if(typeKey == null) {
                    throw new NullPointerException("数据库路由是,决定使用哪个数据源。");
                }
				if(DataSourceType.write.getType().contentEquals(typeKey)) {
					return DataSourceType.write.getType();
				}
				
				int num = count.getAndAdd(1);
				int lookupKey = num % Integer.valueOf(readDataSourceSize);
				System.out.println("使用数据库Read-"+(lookupKey+1));
				return DataSourceType.read.getType()+(lookupKey+1);
			}
		};
		
		Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        //把所有数据库都放在targetDataSources中,注意key值要和determineCurrentLookupKey()中代码写的一至,
        //否则切换数据源时找不到正确的数据源
        targetDataSources.put(DataSourceType.write.getType(), writeDataSource);
        targetDataSources.put(DataSourceType.read.getType()+"1", readDataSource01);
		
		proxy.setDefaultTargetDataSource(writeDataSource);
		proxy.setTargetDataSources(targetDataSources);
		return proxy;
    }

数据源的选择这里,

 

 这里 可以 选择 判断 走那个数据源。

Mybatis 插件实现原理
Mybatis 的插件其实就是个拦截器功能。它利用 JDK 动态代理和责任链设计模式的综合运用。采用责任链
模式,通过动态代理组织多个拦截器 , 通过这些拦截器你可以做一些你想做的事。
自定义拦截器步骤
1. 了解 Mybatis Interceptor 接口
2. 自定义拦截器
3. 全局 xml 配置

可以获取到MappedStatement   可以获取到 sql等并且可以打印出来。

MybatisPlus

官网:MyBatis-Plus (baomidou.com)

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

具体的实现就可以看看 对应官网 就可以了。

分页插件

利用 责任链的链条 对 于具体的sql 进行增加 并 获得创建。

 对于实现来说是很简单 。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

踩踩踩从踩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值