SpringBoot2.0.3+Mybatis+Mysql+druid实现读写分离+事务+切换数据源失败

       mysql支持一主多从,即在写库的数据库发生变动时,会同步到所有从库,只是同步过程中,会有一定的延迟(除非业务中出现,立即写立即读,否则稍微的延迟是可以接收的)。

       mysql的主从复制的配置参考:https://blog.csdn.net/ydyang1126/article/details/70174334

       当数据库有主从之分了,那应用代码也应该读写分离了。这时候的事务就不像单个数据库那么简单了,为此整理出解决事务问题的方案:使用AbstractRoutingDataSource+aop+annotation在service层决定数据源,可以支持事务。此方案的缺点:类内部方法通过this.xx()方式相互调用时,aop不会进行拦截,需进行特殊处理。

       下面对这一方案做详细介绍。

        1、POM.xml

 
	
	    org.springframework.boot
	    spring-boot-starter-parent
	    2.0.3.RELEASE
	     
	
	
	    UTF-8
	    UTF-8
	    1.8
	
	
	
	    
	    
	    
	        org.springframework.boot
	        spring-boot-starter-web
	        
	            
	                org.springframework.boot
	                spring-boot-starter-logging
	            
	        
	    
	    
	    
	    
	        org.springframework.boot
	        spring-boot-starter-log4j2
	    
	    
	    
	    
	    
	        com.fasterxml.jackson.dataformat
	        jackson-dataformat-yaml
	    
	    
	    
	    
		
		    org.springframework.boot
		    spring-boot-devtools
		    true
		    true
		
		
        
        
        
           org.springframework.boot
           spring-boot-starter-aop
        
        
        
        
		
		    org.mybatis.spring.boot
		    mybatis-spring-boot-starter
		    1.3.2
		
		
		
		
		
		    mysql
		    mysql-connector-java
		
		
		
		
		
			org.springframework.boot
			spring-boot-starter-test
			test
		
		
		
		
		    junit
		    junit
		    test
		
		
		
		
           com.alibaba
           druid-spring-boot-starter
           1.1.10
        
        

		
        
        
		    commons-beanutils
		    commons-beanutils
		    1.9.3
		
		
		    commons-collections
		    commons-collections
		    3.2.2
		
		
		    commons-lang
		    commons-lang
		    2.6
		
		
		    commons-logging
		    commons-logging
		    1.2
		
		
		    net.sf.ezmorph
		    ezmorph
		    1.0.6
		
		
		    net.sf.json-lib
		    json-lib
		    2.4
		    jdk15
		
        
        
    

	
	    
		    
	            org.springframework.boot
	            spring-boot-maven-plugin
	            
	                
	                true
	            
	        

	    
	

2、 application.yml和application-dev.yml

        本例子的数据库,都是在本地的mysql中建立2个库,test,test_01,例子是为了测试代码的读写分离。下面是application.yml:

spring: 
  profiles:
    active: dev
  thymeleaf:
    cache: true
    prefix: classpath:/templates/
    suffix: .html
    mode: HTML5
    encoding: UTF-8
    servlet:
      content-type: text/html

mybatis:
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml 

 下面是application-dev.yml:

server:
  port: 8080

spring:
  aop:
      proxy-target-class: true
  datasource:
    #readSize为从库数量
    readSize: 1
    ###################以下为druid增加的配置###########################
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      master:
        url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        min-evictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: true
        testOnReturn: false
        poolPreparedStatements: true
        useGlobalDataSourceStat: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall
        WebStatFilter:
          exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
          enabled: true
          urlPattern: '/*'
        StatViewServlet:
          enabled: true
          urlPattern: '/druid/*'
          loginUsername: druid
          loginPassword: druid
      slave:
        url: jdbc:mysql://127.0.0.1:3306/test_01?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        min-evictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: true
        testOnReturn: false
        poolPreparedStatements: true
        useGlobalDataSourceStat: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall
        WebStatFilter:
          exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
          enabled: true
          urlPattern: '/*'
        StatViewServlet:
          enabled: true
          urlPattern: '/druid/*'
          loginUsername: druid
          loginPassword: druid
    ###############以上为配置druid添加的配置########################################

3、mybatis-config.xml




    
        
        
        
        
        
        
    

4、DataSourceConfiguration.java(读取配置多个数据源) 

package com.wocloud.arch.ssm.mybatis;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class DataSourceConfiguration {
    @Value("${spring.datasource.type}")
    private Class dataSourceType;

    @Bean(name = "writeDataSource", destroyMethod = "close", initMethod = "init")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.druid.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().type(dataSourceType).build();
    }

    @Bean(name = "readDataSource", destroyMethod = "close", initMethod = "init")
    @ConfigurationProperties(prefix = "spring.datasource.druid.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().type(dataSourceType).build();
    }

   @Bean("readDataSources")
    public List readDataSources() throws SQLException {
        List dataSources = new ArrayList<>();
        //dataSources.add(masterDataSource());
        dataSources.add(slaveDataSource());
        return dataSources;
    }
}

5、MybatisConfiguration.java(数据库的sqlSessionFactorys、roundRobinDataSouceProxy、sqlSessionTemplate、annotationDrivenTransactionManager的设置。重点是roundRobinDataSouceProxy()方法,它把所有的数据库源交给MyAbstractRoutingDataSource类,这个类见第6项,并由它的determineCurrentLookupKey()进行决定数据源的选择,其中读库进行了简单的以轮询的方式的负载均衡) 

package com.wocloud.arch.ssm.mybatis;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.wocloud.arch.ssm.utils.SpringContextUtil;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Configuration
@AutoConfigureAfter(DataSourceConfiguration.class)
@ConditionalOnClass({EnableTransactionManagement.class})
@Import({DataSourceConfiguration.class})
@MapperScan(basePackages = {"com.wocloud.arch.ssm.mapper"})
public class MybatisConfiguration {
    @Value("${spring.datasource.type}")
    private Class dataSourceType;
    @Value("${spring.datasource.readSize}")
    private String dataSourceSize;

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(ApplicationContext ac) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(roundRobinDataSouceProxy(ac));
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mybatis/mapper/*.xml"));
        sqlSessionFactoryBean.setTypeAliasesPackage("com.wocloud.arch.ssm.model");
        sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 有多少个数据源就要配置多少个bean
     */
    @Bean
    public AbstractRoutingDataSource roundRobinDataSouceProxy(ApplicationContext ac) {
        int size = Integer.parseInt(dataSourceSize);
        MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource(size);
        Map targetDataSources = new HashMap();

        DataSource writeDataSource = (DataSource) ac.getBean("writeDataSource");
        List readDataSources = (List) ac.getBean("readDataSources");
        for (int i = 0; i < size; i++) {
            targetDataSources.put(i, readDataSources.get(i));
        }
        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"));
    }
    
}

6、MyAbstractRoutingDataSource.java

package com.wocloud.arch.ssm.mybatis;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.util.concurrent.atomic.AtomicInteger;

public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
    private final int dataSourceNumber;
    private AtomicInteger count = new AtomicInteger(0);

    public MyAbstractRoutingDataSource(int dataSourceNumber) {
        this.dataSourceNumber = dataSourceNumber;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        String typeKey = DataSourceContextHolder.getJdbcType();
        if (DataSourceType.write.getType().equals(typeKey) || typeKey == null) {
            return DataSourceType.write.getType();
        }
        // 读 简单负载(因为从库数量为1,实际上目前没有负载效果)
        int number = count.getAndAdd(1);
        int lookupKey = number % dataSourceNumber;
        return new Integer(lookupKey);
    }
}

7、DataSourceType.java()

package com.wocloud.arch.ssm.mybatis;

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;
    }
}

8、DataSourceContextHolder.java

package com.wocloud.arch.ssm.mybatis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DataSourceContextHolder {
    private static final ThreadLocal local = new ThreadLocal();

    public static ThreadLocal getLocal() {
        return local;
    }

    private final static Logger logger = LoggerFactory.getLogger(DataSourceContextHolder.class);

    public static void read() {
        local.set(DataSourceType.read.getType());
        logger.info("切换到读库...");
    }

    public static void write() {
        local.set(DataSourceType.write.getType());
        logger.info("切换到写库...");
    }

    //清除local中的值,用于数据源切换失败的问题
    public static void clear() {
        local.remove();
    }

    public static String getJdbcType() {
        return local.get();
    }
}

 9、写库、读库的注解

package com.wocloud.arch.ssm.mybatis;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadDataSource {
    String description() default "";
}
package com.wocloud.arch.ssm.mybatis;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface WriteDataSource {
    String description() default "";
}

10、 DataSourceAop.java(事务的决定者)

package com.wocloud.arch.ssm.mybatis;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.PriorityOrdered;
import org.springframework.stereotype.Component;

/*
 * 在service层决定数据源
 * 
 * 必须在事务AOP之前执行,所以实现Ordered,order的值越小,越先执行
 * 如果一旦开始切换到写库,则之后的读都会走写库
 */

@Aspect
@EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)
@Component
public class DataSourceAop implements PriorityOrdered {
    private final static Logger logger = LoggerFactory.getLogger(DataSourceAop.class);

    @Pointcut("@annotation(com.wocloud.arch.ssm.mybatis.WriteDataSource)")
    public void writeMethod(){}

    @Pointcut("@annotation(com.wocloud.arch.ssm.mybatis.ReadDataSource)")
    public void readMethod(){}

    @Before("writeMethod() and execution(* com.wocloud.arch.ssm.service.impl..*.*(..)) ")
    public void beforeWrite(JoinPoint point) {
        //设置数据库为写数据
        DataSourceContextHolder.write();
        //调试代码,可注释
        String className = point.getTarget().getClass().getName();
        String methodName = point.getSignature().getName();
        logger.info("dataSource切换到:Write 开始执行:" + className + "." + methodName + "()方法...");
    }

    @Before("readMethod() and execution(* com.wocloud.arch.ssm.service.impl..*.*(..)) ")
    public void beforeRead(JoinPoint point) throws ClassNotFoundException {
        //设置数据库为读数据
        DataSourceContextHolder.read();
        //调试代码,可注释
        String className = point.getTarget().getClass().getName();//根据切点获取当前调用的类名
        String methodName = point.getSignature().getName();//根据切点获取当前调用的类方法
        logger.info("dataSource切换到:Read 开始执行:" + className + "." + methodName + "()方法...");
//        Object[] args = point.getArgs();//根据切点获取当前类方法的参数
//        Class reflexClassName = Class.forName(className);//根据反射获取当前调用类的实例
//        Method[] methods = reflexClassName.getMethods();//获取该实例的所有方法
//        for (Method method : methods) {
//            if (method.getName().equals(methodName)) {
//                String desrciption = method.getAnnotation(ReadDataSource.class).description();//获取该实例方法上注解里面的描述信息
//                System.out.println("desrciption:" + desrciption);
//            }
//        }
    }

    @After("readMethod() || writeMethod()")
    public void after() {
        DataSourceContextHolder.clear();
    }

	@Override
    public int getOrder() {
	 /**
	  * 值越小,越优先执行
	  * 要优于事务的执行
	  * 在启动类中加上了@EnableTransactionManagement(order = 10) 
	  */
	 return 1;
   }
}

11、CdkeyMapper.xml




  
    
    
    
    
    
    
    
    
    
    
    
    
    
  
  
    
    delete from cdkey
    where id = #{id,jdbcType=BIGINT}
  
  
    
    
      SELECT LAST_INSERT_ID()
    
    insert into cdkey (id, cdkey, order_code, 
      isusage, issend, first_time, 
      last_time, order_code_wo, flag1, 
      flag2, flag3, flag4
      )
    values (#{id,jdbcType=BIGINT}, #{cdkey,jdbcType=VARCHAR}, #{orderCode,jdbcType=VARCHAR}, 
      #{isusage,jdbcType=VARCHAR}, #{issend,jdbcType=SMALLINT}, #{firstTime,jdbcType=TIMESTAMP}, 
      #{lastTime,jdbcType=TIMESTAMP}, #{orderCodeWo,jdbcType=VARCHAR}, #{flag1,jdbcType=VARCHAR}, 
      #{flag2,jdbcType=VARCHAR}, #{flag3,jdbcType=INTEGER}, #{flag4,jdbcType=INTEGER}
      )
  
  
    
    update cdkey
    set cdkey = #{cdkey,jdbcType=VARCHAR},
      order_code = #{orderCode,jdbcType=VARCHAR},
      isusage = #{isusage,jdbcType=VARCHAR},
      issend = #{issend,jdbcType=SMALLINT},
      first_time = #{firstTime,jdbcType=TIMESTAMP},
      last_time = #{lastTime,jdbcType=TIMESTAMP},
      order_code_wo = #{orderCodeWo,jdbcType=VARCHAR},
      flag1 = #{flag1,jdbcType=VARCHAR},
      flag2 = #{flag2,jdbcType=VARCHAR},
      flag3 = #{flag3,jdbcType=INTEGER},
      flag4 = #{flag4,jdbcType=INTEGER}
    where id = #{id,jdbcType=BIGINT}
  
  
  
  
  
  

 12、CdkeyMapper.java

package com.wocloud.arch.ssm.mapper;

import com.wocloud.arch.ssm.model.Cdkey;
import java.util.List;

import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CdkeyMapper {
    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table cdkey
     *
     * @mbggenerated
     */
    int deleteByPrimaryKey(Long id);

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table cdkey
     *
     * @mbggenerated
     */
    int insert(Cdkey record);

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table cdkey
     *
     * @mbggenerated
     */
    Cdkey selectByPrimaryKey(Long id);

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table cdkey
     *
     * @mbggenerated
     */
    List selectAll();

    /**
     * This method was generated by MyBatis Generator.
     * This method corresponds to the database table cdkey
     *
     * @mbggenerated
     */
    int updateByPrimaryKey(Cdkey record);
    /*
     * 查询可用兑换码
     */
    Cdkey selectCdkeyByIsUsage(String isUsage);
    
    /*
     * 由订单号查询兑换码是否已发送
     */
    Cdkey selectCdkeyByOrderCode(String orderCode);
}

13、CdkeyService.java

/**
 * 
 */
package com.wocloud.arch.ssm.service;

import java.util.List;

import com.wocloud.arch.ssm.model.Cdkey;

/**
 * @author mazhen
 *
 */
public interface CdkeyService {
	/*
     * 插入Cdkey信息
     * @param Cdkey
     * @return 
     */
	public int insertCdkey(Cdkey cdkey);
	
	/*
     * 查询Cdkey信息
     * @param cdkeyId
     * @return  Cdkey
     */
	public Cdkey queryByCdkeyId(Long cdkeyId);
	
	/*
     * 删除Cdkey信息
     * @param cdkeyId
     * @return  
     */
	public int deleteByCdkeyId(Long cdkeyId);
	
	/*
     * 更新Cdkey信息
     * @param Cdkey
     * @return  
     */
	public int updateCdkey(Cdkey cdkey);
	
	/*
     * 查询所有Cdkey信息
     * @param 
     * @return  
     */
	public List queryAll();
	
	/*
     * 根据短信状态查询可用Cdkey兑换码
     * @param messageStatus
     * @return  Cdkey
     */
	public Cdkey queryCdkeyByIsUsage(String isUsage);
	
	/*
     * 根据订单号查询Cdkey兑换码是否已存在
     * @param orderCode
     * @return  Cdkey
     */
	public Cdkey queryCdkeyByOrderCode(String orderCode);
    
    /*
	 * 兑换码是否使用,cdkey表中更新数据
	 */
	public String updateCdkeyIsusage(JSONObject jsonObject);
}

14、CdkeyServiceImpl.java(使用getService()的原因:

https://www.oschina.net/translate/comparative_analysis_between_spring_aop_and_aspectj)

/**
 * 
 */
package com.wocloud.arch.ssm.service.impl;

import java.util.List;

import com.wocloud.arch.ssm.mybatis.ReadDataSource;
import com.wocloud.arch.ssm.mybatis.WriteDataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import com.wocloud.arch.ssm.mapper.CdkeyMapper;
import com.wocloud.arch.ssm.model.Cdkey;
import com.wocloud.arch.ssm.service.CdkeyService;
import com.wocloud.arch.ssm.utils.SpringContextUtil;


import net.sf.json.JSONObject;


/**
 * @author mazhen
 * 注:AOP ,内部方法之间互相调用时,如果是this.xxx()这形式,不会触发AOP拦截,可能会
 * 导致无法决定数据库是走写库还是读库
 * 方法:
 * 为了触发AOP的拦截,调用内部方法时,需要特殊处理下,看方法getService()
 */
@Service
public class CdkeyServiceImpl implements CdkeyService {
	
	private final static Logger logger = LoggerFactory.getLogger(CdkeyParameterServiceImpl.class);
	
	@Autowired
	private CdkeyMapper cdkeyMapper;

	@Override
	@WriteDataSource(description="WRITE")
	public int insertCdkey(Cdkey cdkey) {
		return cdkeyMapper.insert(cdkey);
	}
	
	@Override
	@ReadDataSource(description="READ")
	public Cdkey queryByCdkeyId(Long cdkeyId) {
		return cdkeyMapper.selectByPrimaryKey(cdkeyId);
	}
	
	@Override
	@WriteDataSource(description="WRITE")
	public int deleteByCdkeyId(Long cdkeyId) {
		return cdkeyMapper.deleteByPrimaryKey(cdkeyId);
	}

	@Override
	@WriteDataSource(description="WRITE")
	public int updateCdkey(Cdkey cdkey) {
		return cdkeyMapper.updateByPrimaryKey(cdkey);
	}

	@Override
	@ReadDataSource(description="READ")
	public List queryAll() {
		return cdkeyMapper.selectAll();
	}

	@Override
	@ReadDataSource(description="READ")
	public Cdkey queryCdkeyByIsUsage(String isUsage) {
		return cdkeyMapper.selectCdkeyByIsUsage(isUsage);
	}

	@Override
	@ReadDataSource(description="READ")
	public Cdkey queryCdkeyByOrderCode(String orderCode) {
		return cdkeyMapper.selectCdkeyByOrderCode(orderCode);
	}

	@Override
	@Transactional
	public String updateCdkeyIsusage(JSONObject jsonObject) {
		String cdkeyStr = null;
		
		Cdkey cdkey = getService().queryCdkeyByIsUsage("0");
	    if (null == cdkey) {
	    	logger.info("兑换码已用完");
	    	return cdkeyStr;
	    }
		
		cdkey.setOrderCode(jsonObject.getString("orderCode"));
		cdkey.setIsusage(jsonObject.getString("telephone"));
		cdkey.setLastTime(RandomTool.getMillisecond());
	
		
		try {
			if (getService().updateCdkey(cdkey) > 0) {
				cdkeyStr = cdkey.getCdkey();
			}
		} catch (Exception e) {
			logger.info("异常信息:"+e);
		}
		return cdkeyStr;
	}
	
	private CdkeyServiceImpl getService(){
		return SpringContextUtil.getBean(this.getClass());
	}


}

15、SpringContextUtil.java

package com.wocloud.arch.ssm.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringContextUtil implements ApplicationContextAware{

	private static ApplicationContext applicationContext = null;
	
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		if(SpringContextUtil.applicationContext == null){
			SpringContextUtil.applicationContext = applicationContext;
		}
		
	}

	public static ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	
	public static Object getBean(String name){
		return getApplicationContext().getBean(name);
	}
	
	public static  T getBean(Class clazz){
		return getApplicationContext().getBean(clazz);
	}
	
}

16、写一个Controller进行验证:

/**
 * 
 */
package com.wocloud.arch.ssm.controller;

import java.io.IOException;
import java.security.DigestException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;


import com.wocloud.arch.ssm.model.CdkeyParameter;
import com.wocloud.arch.ssm.responseResult.ResponseData;
import com.wocloud.arch.ssm.responseResult.ResponseResult;
import com.wocloud.arch.ssm.service.CdkeyParameterService;
import com.wocloud.arch.ssm.service.CdkeyService;

import net.sf.json.JSONObject;


/**
 * @author mazhen
 * 兑换码请求接口
 */
@RestController
public class CdkeyController {
	/**
	 * 引入日志,注意都是"org.slf4j"包下
	 */
	private final static Logger logger = LoggerFactory.getLogger(CdkeyController.class);
  
    @Autowired
    private CdkeyParameterService cdkeyParameterService;
    @Autowired
    private CdkeyService cdkeyService;
    
	/*
	 * 兑换码post请求
	 * @param HttpServletRequest request
	 * @result ResponseResult
	 */
	@RequestMapping(value = "cdkeySender",method = RequestMethod.POST)
	public ResponseData cdkeySender(HttpServletRequest request) {
		logger.info("开始接收合作伙伴的参数");
		Map map = new HashMap<>();
		JSONObject jsonObjectData = null;
		JSONObject jsonObject = null;
		
		try {
			jsonObjectData = ParameterUtil.getParameters(request);
		} catch (IOException e) {
			e.printStackTrace();
			logger.error("接口获取参数异常:"+e);
			return responseDataFromContent(
					"3",
					"failure",
					null,
					"接口异常");
		}

		/*
		 * 校验json包体完整性
		 */
		logger.info("从合作伙伴获取到的jsonObjectData:"+jsonObjectData);
		if (null == jsonObjectData || ("").equals(jsonObjectData) || !jsonObjectData.containsKey("data")) {
			logger.error("接口获取参数失败");
			return responseDataFromContent(
					"4",
					"failure",
					null,
					"接口获取参数失败");
		}
		
		logger.info(jsonObjectData.getString("data"));
		jsonObject = JSONObject.fromObject(jsonObjectData.getString("data"));
		String telePhone = jsonObject.getString("telephone");
		
		
		try {
				return updateCdkey(jsonObject);
		} catch (DigestException e) {
			e.printStackTrace();
			logger.error("接口签名算法异常:"+e);
			return responseDataFromContent(
					"5",
					"failure",
					null,
					"接口签名算法异常");
		}
	}

	/*
	 * 处理Response数据
	 */
	private ResponseData responseDataFromContent(final String code, String message, String telePhone, String detail) {
		ResponseData responseData = new ResponseData();
		ResponseResult result = new ResponseResult();
		result.setCode(code);
		result.setMessage(message);
		if (telePhone != null) {
			result.setTelephone(telePhone);
		}
		result.setDetail(detail);
		responseData.setData(result);
		responseData.setResponseTime(RandomTool.getTimeStamp());
		return responseData;
	}
	
	
	/*
	 * 更新cdkey数据库
	 */
	public ResponseData updateCdkey(JSONObject jsonObject) {
        ResponseData responseData = null;
			
		String cdkeyStr = cdkeyService.updateCdkeyIsusage(jsonObject);
        
        if (cdkeyStr == null) {
			logger.info("cdkey表更新失败,短信发送失败!");
			responseData = responseDataFromContent(
						"11",
						"failure",
						jsonObject.getString("telephone"),
						"数据处理失败");
	   } else {
           logger.info("cdkey表更新成功,短信发送成功!");
		   responseData = responseDataFromContent(
							"0",
							"success",
							jsonObject.getString("telephone"),
							"请求成功")
       }
       return responseData;
						
	}

}

17、ResponseResult.java

/**
 * 
 */
package com.wocloud.arch.ssm.responseResult;

/**
 * @author mazhen
 *
 */
public class ResponseData {
	
    private ResponseResult data;
    
    private String responseTime;

	public ResponseResult getData() {
		return data;
	}

	public void setData(ResponseResult data) {
		this.data = data;
	}

	public String getResponseTime() {
		return responseTime;
	}

	public void setResponseTime(String responseTime) {
		this.responseTime = responseTime;
	}
}

18、ResponseData.java

/**
 * 
 */
package com.wocloud.arch.ssm.responseResult;

/**
 * @author mazhen
 *
 */
public class ResponseResult {
    private String code;
	
	private String message;
	
	private String telephone;
	
	private String detail;

	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
		
	
	public String getTelephone() {
		return telephone;
	}

	public void setTelephone(String telephone) {
		this.telephone = telephone;
	}

	public String getDetail() {
		return detail;
	}

	public void setDetail(String detail) {
		this.detail = detail;
	}

	public ResponseResult(String code,String message,String telephone,String detail){
		super();
		this.code = code;
		this.message = message;
		this.telephone = telephone;
		this.detail = detail;
	}
	public ResponseResult(){
		
	}
}

19、ParameterUtil.java

/**
 * 
 */
package com.wocloud.arch.ssm.utils.common;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.http.HttpServletRequest;

import net.sf.json.JSONObject;

/**
 * @author mazhen
 * 获取post请求中的body内容
 */
public class ParameterUtil {

    public static JSONObject getParameters(HttpServletRequest request) throws IOException {
        //从request中以"UTF-8"形式获取输入流,避免中文乱码
        BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));
        StringBuffer sb = new StringBuffer("");
        String temp;
        //一行一行读取并放入到StringBuffer中
        while((temp = br.readLine()) != null){
            sb.append(temp);
        }
        br.close();
        String acceptjson = sb.toString();
        JSONObject jo = null;
        //把String转成JSON对象
        if (acceptjson != "") {
            jo = JSONObject.fromObject(acceptjson);
        }
        return jo;
    }

}

 最后,使用apache bench进行测试,事务的生效如图所示:

SpringBoot2.0.3+Mybatis+Mysql+druid实现读写分离+事务+切换数据源失败_第1张图片

 代码参见:https://github.com/mameng1992/SpringBoot2.0.3_Mybatis_Read_Write

参考文章:1、https://blog.csdn.net/qq_37061442/article/details/82350258

                  2、https://blog.csdn.net/Appleyk/article/details/79442006

你可能感兴趣的:(mysql,mybatis,事务,SpringBoot)