spring boot学习7之mybatis+mysql读写分离(一写多读)+事务

来源:互联网 发布:冒充淘宝客服退款诈骗 编辑:程序博客网 时间:2024/06/06 20:57

        当业务的访问量(数据库的查询)非常大时,为了降低数据库的压力,希望有多个数据库进行负载均衡,避免所有的查询都集中在一台数据库,造成数据库压力过大。mysql支持一主多从,即在写库的数据库发生变动时,会同步到所有从库,只是同步过程中,会有一定的延迟(除非业务中出现,立即写立即读,否则稍微的延迟是可以接收的)。

       当数据库有主从之分了,那应用代码也应该读写分离了。那代码执行时,该如何决定选择哪个数据库呢。

方案一:

       就像配置多个数据源那样(见博文spring boot学习6之mybatis+PageHelper分页插件+jta多数据源事务整合),将dao都分别放到不通的包下,指明哪个包下dao接口或配置文件走哪个数据库,service层程序员决定走主库还是从库。

       缺点:相同的dao接口和配置文件要复制多份到不同包路径下,不易维护和扩展。

方案二:

       使用AbstractRoutingDataSource+aop+annotation在dao层决定数据源。

        缺点:不支持事务。因为事务在service层开启时,就必须拿到数据源了。

方案三:

      使用AbstractRoutingDataSource+aop+annotation在service层决定数据源,可以支持事务.

      缺点:类内部方法通过this.xx()方式相互调用时,aop不会进行拦截,需进行特殊处理。


     方案二和方案三的区别就是数据源的决定是方案dao还是service,所以本博文例子代码会都含有。

    源码下载,见github。


项目结构


pom.xml

<parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>1.5.2.RELEASE</version></parent>     <dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>      <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><dependency>    <groupId>com.alibaba</groupId>    <artifactId>druid</artifactId>    <version>1.0.29</version></dependency><dependency>            <groupId>org.aspectj</groupId>            <artifactId>aspectjweaver</artifactId>        </dependency>        <dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-beans</artifactId></dependency><!-- 分页插件 --><dependency>    <groupId>com.github.pagehelper</groupId>    <artifactId>pagehelper</artifactId>    <version>4.1.6</version></dependency></dependencies>

本例子的数据库,都是在本地的mysql中建立3个库,test,test_01,test_02,例子是为了测试代码的读写分离,而是mysqld

application.yml

logging:  config: classpath:logback.xml  path: d:/logsserver:  port: 80  session-timeout: 60mybatis:     mapperLocations: classpath:/com/fei/springboot/dao/*.xml     typeAliasesPackage: com.fei.springboot.dao         mapperScanPackage: com.fei.springboot.dao     configLocation: classpath:/mybatis-config.xmlmysql:    datasource:        readSize: 2  #读库个数        type: com.alibaba.druid.pool.DruidDataSource        mapperLocations: classpath:/com/fei/springboot/dao/*.xml        configLocation: classpath:/mybatis-config.xml        write:           url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8           username: root           password: root           driver-class-name: com.mysql.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_01?useUnicode=true&characterEncoding=utf-8           username: root           password: root           driver-class-name: com.mysql.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        read02:           url: jdbc:mysql://127.0.0.1:3306/test_02?useUnicode=true&characterEncoding=utf-8           username: root           password: root           driver-class-name: com.mysql.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  
mybatis-config.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><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></configuration>

读取配置多个数据源

DataSourceConfiguration.java

package com.fei.springboot.config.dbconfig;import javax.sql.DataSource;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.boot.web.servlet.ServletRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import com.alibaba.druid.support.http.StatViewServlet;import com.alibaba.druid.support.http.WebStatFilter;/** * 数据库源配置 * @author Jfei * */@Configurationpublic 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();    }    @Bean(name = "readDataSource02")    @ConfigurationProperties(prefix = "mysql.datasource.read02")    public DataSource readDataSourceTwo() {        log.info("-------------------- read02 DataSourceTwo init ---------------------");        return DataSourceBuilder.create().type(dataSourceType).build();    }        }


数据库的sqlSessionFactorys、roundRobinDataSouceProxy、sqlSessionTemplate、annotationDrivenTransactionManager的设置

MybatisConfiguration.java

package com.fei.springboot.config.dbconfig;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.Properties;import java.util.concurrent.atomic.AtomicInteger;import javax.sql.DataSource;import org.apache.ibatis.plugin.Interceptor;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.SqlSessionTemplate;import org.mybatis.spring.annotation.MapperScan;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.autoconfigure.AutoConfigureAfter;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.DefaultResourceLoader;import org.springframework.core.io.Resource;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import org.springframework.transaction.PlatformTransactionManager;import com.fei.springboot.util.SpringContextUtil;import com.github.pagehelper.PageHelper;@Configuration@AutoConfigureAfter(DataSourceConfiguration.class)@MapperScan(basePackages="com.fei.springboot.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;@Autowired@Qualifier("readDataSource02")private DataSource readDataSource02;    @Bean(name="sqlSessionFactory")    public SqlSessionFactory sqlSessionFactorys() throws Exception {        log.info("--------------------  sqlSessionFactory init ---------------------");        try {            SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();       //     sessionFactoryBean.setDataSource(roundRobinDataSouce);            sessionFactoryBean.setDataSource(roundRobinDataSouceProxy());                        // 读取配置             sessionFactoryBean.setTypeAliasesPackage("com.fei.springboot.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;        }    }    /**     * 分页插件     * @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;    }    /**     * 把所有数据库都放在路由中     * @return     */    @Bean(name="roundRobinDataSouceProxy")    public AbstractRoutingDataSource roundRobinDataSouceProxy() {        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();        //把所有数据库都放在targetDataSources中,注意key值要和determineCurrentLookupKey()中代码写的一至,        //否则切换数据源时找不到正确的数据源        targetDataSources.put(DataSourceType.write.getType(), writeDataSource);        targetDataSources.put(DataSourceType.read.getType()+"1", readDataSource01);        targetDataSources.put(DataSourceType.read.getType()+"2", readDataSource02);            final int readSize = Integer.parseInt(readDataSourceSize);   //     MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource(readSize);                //路由类,寻找对应的数据源        AbstractRoutingDataSource proxy = new AbstractRoutingDataSource(){         private AtomicInteger count = new AtomicInteger(0);        /**             * 这是AbstractRoutingDataSource类中的一个抽象方法,             * 而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,             * targetDataSources就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。             */        @Override        protected Object determineCurrentLookupKey() {        String typeKey = DataSourceContextHolder.getReadOrWrite();                if(typeKey == null){        //System.err.println("使用数据库write.............");                //    return DataSourceType.write.getType();        throw new NullPointerException("数据库路由时,决定使用哪个数据库源类型不能为空...");        }                        if (typeKey.equals(DataSourceType.write.getType())){                System.err.println("使用数据库write.............");                    return DataSourceType.write.getType();                }                                //读库, 简单负载均衡                int number = count.getAndAdd(1);                int lookupKey = number % readSize;                System.err.println("使用数据库read-"+(lookupKey+1));                return DataSourceType.read.getType()+(lookupKey+1);        }        };                proxy.setDefaultTargetDataSource(writeDataSource);//默认库        proxy.setTargetDataSources(targetDataSources);        return proxy;    }    @Bean    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {    return new SqlSessionTemplate(sqlSessionFactory);    }        //事务管理    @Bean    public PlatformTransactionManager annotationDrivenTransactionManager() {        return new DataSourceTransactionManager((DataSource)SpringContextUtil.getBean("roundRobinDataSouceProxy"));    }    }

   重点是roundRobinDataSouceProxy()方法,它把所有的数据库源交给AbstractRoutingDataSource类,并由它的determineCurrentLookupKey()进行决定数据源的选择,其中读库进行了简单的负载均衡(轮询)。

DataSourceType.java

package com.fei.springboot.config.dbconfig;public enum DataSourceType {read("read", "从库"),write("write", "主库");    private String type;        private String name;    DataSourceType(String type, String name) {        this.type = type;        this.name = name;    }public String getType() {return type;}public void setType(String type) {this.type = type;}public String getName() {return name;}public void setName(String name) {this.name = name;}    }
DataSourceContextHolder.java

package com.fei.springboot.config.dbconfig;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 本地线程,数据源上下文 * @author Jfei * */public class DataSourceContextHolder {private static Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);//线程本地环境private static final ThreadLocal<String> local = new ThreadLocal<String>();    public static ThreadLocal<String> getLocal() {        return local;    }    /**     * 读库     */    public static void setRead() {        local.set(DataSourceType.read.getType());        log.info("数据库切换到读库...");    }    /**     * 写库     */    public static void setWrite() {        local.set(DataSourceType.write.getType());        log.info("数据库切换到写库...");    }    public static String getReadOrWrite() {        return local.get();    }        public static void clear(){    local.remove();    }}

写库、读库的注解

package com.fei.springboot.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Inherited;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface ReadDataSource {}
package com.fei.springboot.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Inherited;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface WriteDataSource {}

UserMapper.java

package com.fei.springboot.dao;import java.util.List;import org.apache.ibatis.annotations.Insert;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Param;import org.apache.ibatis.annotations.Select;import com.fei.springboot.domain.User;@Mapperpublic interface UserMapper {@Insert("insert sys_user(id,user_name) values(#{id},#{userName})")void insert(User u);@Select("select id,user_name from sys_user where id=#{id} ")User findById(@Param("id")String id);//注:方法名和要UserMapper.xml中的id一致List<User> query(@Param("userName")String userName);}

如果想在dao进行数据源的决定,在aop的拦截路径写明是dao

DataSourceAopInDao.java

package com.fei.springboot.aop;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import com.fei.springboot.config.dbconfig.DataSourceContextHolder;import com.fei.springboot.config.dbconfig.DataSourceType;/** * 在dao层决定数据源(注:如果用这方式,service层不能使用事务,否则出问题,因为打开事务打开时,就会觉得数据库源了) * @author Jfei * *///@Aspect//@Componentpublic class DataSourceAopInDao {private static Logger log = LoggerFactory.getLogger(DataSourceAopInDao.class);@Before("execution(* com.fei.springboot.dao..*.find*(..)) "+ " or execution(* com.fei.springboot.dao..*.get*(..)) "+ " or execution(* com.fei.springboot.dao..*.query*(..))")    public void setReadDataSourceType() {        DataSourceContextHolder.setRead();    }    @Before("execution(* com.fei.springboot.dao..*.insert*(..)) "    + " or execution(* com.fei.springboot.dao..*.update*(..))"    + " or execution(* com.fei.springboot.dao..*.add*(..))")    public void setWriteDataSourceType() {        DataSourceContextHolder.setWrite();    }        /*    @Before("execution(* com.fei.springboot.dao..*.*(..)) "+ " and @annotation(com.fei.springboot.annotation.ReadDataSource) ")public void setReadDataSourceType() {//如果已经开启写事务了,那之后的所有读都从写库读if(!DataSourceType.write.getType().equals(DataSourceContextHolder.getReadOrWrite())){DataSourceContextHolder.setRead();}    }@Before("execution(* com.fei.springboot.dao..*.*(..)) "+ " and @annotation(com.fei.springboot.annotation.WriteDataSource) ")public void setWriteDataSourceType() {    DataSourceContextHolder.setWrite();}*/}


如果是service,必须实现Ordered,并且优先级优于事务的开启。

DataSourceAopInService.java

package com.fei.springboot.aop;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.context.annotation.EnableAspectJAutoProxy;import org.springframework.core.PriorityOrdered;import org.springframework.stereotype.Component;import org.springframework.transaction.annotation.EnableTransactionManagement;import com.fei.springboot.config.dbconfig.DataSourceContextHolder;import com.fei.springboot.config.dbconfig.DataSourceType;/** * 在service层觉得数据源 *  * 必须在事务AOP之前执行,所以实现Ordered,order的值越小,越先执行 * 如果一旦开始切换到写库,则之后的读都会走写库 *  * @author Jfei * */@Aspect@EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)@Componentpublic class DataSourceAopInService implements PriorityOrdered{private static Logger log = LoggerFactory.getLogger(DataSourceAopInService.class);/*@Before("execution(* com.fei.springboot.service..*.find*(..)) "+ " or execution(* com.fei.springboot.service..*.get*(..)) "+ " or execution(* com.fei.springboot.service..*.query*(..))")    public void setReadDataSourceType() {//如果已经开启写事务了,那之后的所有读都从写库读if(!DataSourceType.write.getType().equals(DataSourceContextHolder.getReadOrWrite())){DataSourceContextHolder.setRead();}            }    @Before("execution(* com.fei.springboot.service..*.insert*(..)) "    + " or execution(* com.fei.springboot.service..*.update*(..))"    + " or execution(* com.fei.springboot.service..*.add*(..))")    public void setWriteDataSourceType() {        DataSourceContextHolder.setWrite();    }*/    @Before("execution(* com.fei.springboot.service..*.*(..)) "+ " and @annotation(com.fei.springboot.annotation.ReadDataSource) ")public void setReadDataSourceType() {//如果已经开启写事务了,那之后的所有读都从写库读if(!DataSourceType.write.getType().equals(DataSourceContextHolder.getReadOrWrite())){DataSourceContextHolder.setRead();}    }@Before("execution(* com.fei.springboot.service..*.*(..)) "+ " and @annotation(com.fei.springboot.annotation.WriteDataSource) ")public void setWriteDataSourceType() {    DataSourceContextHolder.setWrite();}    @Overridepublic int getOrder() {/** * 值越小,越优先执行 * 要优于事务的执行 * 在启动类中加上了@EnableTransactionManagement(order = 10)  */return 1;}}



UserService.java

package com.fei.springboot.service;import org.springframework.aop.framework.AopContext;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.EnableAspectJAutoProxy;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Isolation;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import com.fei.springboot.annotation.ReadDataSource;import com.fei.springboot.annotation.WriteDataSource;import com.fei.springboot.dao.UserMapper;import com.fei.springboot.domain.User;import com.fei.springboot.util.SpringContextUtil;import com.github.pagehelper.Page;import com.github.pagehelper.PageHelper;import com.github.pagehelper.PageInfo;/** * 如果需要事务,自行在方法上添加@Transactional * 如果方法有内部有数据库操作,则必须指定@WriteDataSource还是@ReadDataSource *  * 注:AOP ,内部方法之间互相调用时,如果是this.xxx()这形式,不会触发AOP拦截,可能会 * 导致无法决定数据库是走写库还是读库 * 方法: * 为了触发AOP的拦截,调用内部方法时,需要特殊处理下,看方法getService() *  * @author Jfei * */@Servicepublic class UserService {@Autowiredprivate UserMapper userMapper;@WriteDataSource@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)public void insertUser(User u){this.userMapper.insert(u);//如果类上面没有@Transactional,方法上也没有,哪怕throw new RuntimeException,数据库也会成功插入数据//throw new RuntimeException("测试插入事务");}/** * 写事务里面调用读 * @param u */public void wirteAndRead(User u){getService().insertUser(u);//这里走写库,那后面的读也都要走写库//这是刚刚插入的User uu = getService().findById(u.getId());System.out.println("==读写混合测试中的读(刚刚插入的)====id="+u.getId()+",  user_name=" + uu.getUserName());//为了测试,3个库中id=1的user_name是不一样的User uuu = getService().findById("1");System.out.println("==读写混合测试中的读====id=1,  user_name=" + uuu.getUserName());}public void readAndWirte(User u){//为了测试,3个库中id=1的user_name是不一样的User uu = getService(). findById("1");System.out.println("==读写混合测试中的读====id=1,user_name=" + uu.getUserName());getService().insertUser(u);}@ReadDataSourcepublic User findById(String id){User u = this.userMapper.findById(id);return u;}@ReadDataSourcepublic PageInfo<User> queryPage(String userName,int pageNum,int pageSize){Page<User> page = PageHelper.startPage(pageNum, pageSize);//PageHelper会自动拦截到下面这查询sqlthis.userMapper.query(userName);return page.toPageInfo();}private UserService getService(){// 采取这种方式的话,//@EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)//必须设置为true/*if(AopContext.currentProxy() != null){return (UserService)AopContext.currentProxy();}else{return this;}*/return SpringContextUtil.getBean(this.getClass());}}


   开头的方案三中也说到了,如果aop拦截在service层,但是aop不会拦截类内部的调用,至于原因,看博文http://www.oschina.net/translate/comparative_analysis_between_spring_aop_and_aspectj
  所以UserService中增加了getService()方法进行处理。


   写个controller进行简单测试

UserController.java

package com.fei.springboot.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import com.fei.springboot.domain.User;import com.fei.springboot.service.UserService;import com.github.pagehelper.PageInfo;@Controller@RequestMapping("/user")public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/hello")@ResponseBodypublic String hello(){return "hello";}/** * 测试插入 * @return */@RequestMapping("/add")@ResponseBodypublic String add(String id,String userName){User u = new User();u.setId(id);u.setUserName(userName);this.userService.insertUser(u);return u.getId()+"    " + u.getUserName();}/** * 测试读 * @param id * @return */@RequestMapping("/get/{id}")@ResponseBodypublic String findById(@PathVariable("id") String id){User u = this.userService.findById(id);return u.getId()+"    " + u.getUserName();}/** * 测试写然后读 * @param id * @param userName * @return */@RequestMapping("/addAndRead")@ResponseBodypublic String addAndRead(String id,String userName){User u = new User();u.setId(id);u.setUserName(userName);this.userService.wirteAndRead(u);return u.getId()+"    " + u.getUserName();}/** * 测试读然后写 * @param id * @param userName * @return */@RequestMapping("/readAndAdd")@ResponseBodypublic String readAndWrite(String id,String userName){User u = new User();u.setId(id);u.setUserName(userName);this.userService.readAndWirte(u);return u.getId()+"    " + u.getUserName();}/** * 测试分页插件 * @return */@RequestMapping("/queryPage")@ResponseBodypublic String queryPage(){PageInfo<User> page = this.userService.queryPage("tes", 1, 2);StringBuilder sb = new StringBuilder();sb.append("<br/>总页数=" + page.getPages());sb.append("<br/>总记录数=" + page.getTotal()) ;for(User u : page.getList()){sb.append("<br/>" + u.getId() + "      " + u.getUserName());}System.out.println("分页查询....\n" + sb.toString());return sb.toString();}}

http://127.0.0.1/user/get/1  时,获取到的结果要么是库test_01要么是test_02中来的,其他方法也可以测试均符合预期结果。这里就不一一贴结果了。其他的类源码也不一一贴出来了。想看的可以到github上看或下载。

   完整源码













阅读全文
1 0
原创粉丝点击