getLocal() {
11 | return local;
12 | }
13 |
14 | /**
15 | * 读可能是多个库
16 | */
17 | public static void read() {
18 | local.set(DataSourceType.read.getType());
19 | }
20 |
21 | /**
22 | * 写只有一个库
23 | */
24 | public static void write() {
25 | local.set(DataSourceType.write.getType());
26 | }
27 |
28 | public static String getJdbcType() {
29 | return local.get();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/chengbinbbs/config/mybatis/DataSourceType.java:
--------------------------------------------------------------------------------
1 | package com.chengbinbbs.config.mybatis;
2 |
3 | /**
4 | * @author zhangcb
5 | * @created on 2017-07-10 17:30.
6 | */
7 | public enum DataSourceType {
8 | read("read", "从库"), write("write", "主库");
9 |
10 | private String type;
11 |
12 | private String name;
13 |
14 | public String getType() {
15 | return type;
16 | }
17 |
18 | public void setType(String type) {
19 | this.type = type;
20 | }
21 |
22 | public String getName() {
23 | return name;
24 | }
25 |
26 | public void setName(String name) {
27 | this.name = name;
28 | }
29 |
30 | DataSourceType(String type, String name) {
31 | this.type = type;
32 | this.name = name;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/chengbinbbs/config/mybatis/MyAbstractRoutingDataSource.java:
--------------------------------------------------------------------------------
1 | package com.chengbinbbs.config.mybatis;
2 |
3 | import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
4 |
5 | import java.util.concurrent.atomic.AtomicInteger;
6 |
7 | /**
8 | * @author zhangcb
9 | * @created 2017-07-10 17:33.
10 | */
11 | public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
12 | private final int dataSourceNumber;
13 | private AtomicInteger count = new AtomicInteger(0);
14 |
15 | public MyAbstractRoutingDataSource(int dataSourceNumber) {
16 | this.dataSourceNumber = dataSourceNumber;
17 | }
18 |
19 | @Override
20 | protected Object determineCurrentLookupKey() {
21 | String typeKey = DataSourceContextHolder.getJdbcType();
22 | if (typeKey.equals(DataSourceType.write.getType()))
23 | return DataSourceType.write.getType();
24 | // 读 简单负载均衡
25 | int number = count.getAndAdd(1);
26 | int lookupKey = number % dataSourceNumber;
27 | return new Integer(lookupKey);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/chengbinbbs/config/mybatis/MyDataSourceTransactionManager.java:
--------------------------------------------------------------------------------
1 | package com.chengbinbbs.config.mybatis;
2 |
3 | import com.atomikos.icatch.jta.UserTransactionImp;
4 | import com.atomikos.icatch.jta.UserTransactionManager;
5 | import org.apache.commons.logging.Log;
6 | import org.apache.commons.logging.LogFactory;
7 | import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 | import org.springframework.context.annotation.DependsOn;
11 | import org.springframework.transaction.PlatformTransactionManager;
12 | import org.springframework.transaction.annotation.EnableTransactionManagement;
13 | import org.springframework.transaction.jta.JtaTransactionManager;
14 |
15 | import javax.transaction.TransactionManager;
16 | import javax.transaction.UserTransaction;
17 |
18 | /**
19 | * @author zhangcb
20 | * @created 2017-07-10 17:37.
21 | */
22 | @Configuration
23 | @EnableTransactionManagement
24 | public class MyDataSourceTransactionManager extends DataSourceTransactionManagerAutoConfiguration {
25 |
26 | private static Log logger = LogFactory.getLog(MyDataSourceTransactionManager.class);
27 |
28 | /**
29 | * 自定义事务
30 | * MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
31 | * @return
32 | */
33 | // @Resource(name = "writeDataSource")
34 | // private DataSource dataSource;
35 | //
36 | // @Bean(name = "transactionManager")
37 | // public DataSourceTransactionManager transactionManagers() {
38 | // logger.info("-------------------- transactionManager init ---------------------");
39 | // return new DataSourceTransactionManager(dataSource);
40 | // }
41 |
42 | /**
43 | * 分布式XA事务
44 | * @return
45 | * @throws Throwable
46 | */
47 | @Bean(name = "userTransaction")
48 | public UserTransaction userTransaction() throws Throwable {
49 | UserTransactionImp userTransactionImp = new UserTransactionImp();
50 | userTransactionImp.setTransactionTimeout(10000);
51 | return userTransactionImp;
52 | }
53 | @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
54 | public TransactionManager atomikosTransactionManager() throws Throwable {
55 | UserTransactionManager userTransactionManager = new UserTransactionManager();
56 | userTransactionManager.setForceShutdown(false);
57 | return userTransactionManager;
58 | }
59 | @Bean(name = "transactionManager")
60 | @DependsOn({ "userTransaction", "atomikosTransactionManager" })
61 | public PlatformTransactionManager transactionManager() throws Throwable {
62 | UserTransaction userTransaction = userTransaction();
63 | JtaTransactionManager manager = new JtaTransactionManager(userTransaction,atomikosTransactionManager());
64 | return manager;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/chengbinbbs/controller/UserController.java:
--------------------------------------------------------------------------------
1 | package com.chengbinbbs.controller;
2 |
3 | import com.chengbinbbs.model.User;
4 | import com.chengbinbbs.service.UserService;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.validation.BindingResult;
7 | import org.springframework.web.bind.annotation.PathVariable;
8 | import org.springframework.web.bind.annotation.RequestMapping;
9 | import org.springframework.web.bind.annotation.RestController;
10 |
11 | import javax.validation.Valid;
12 |
13 | /**
14 | * 用户中心
15 | *
16 | * @author zhangcb
17 | * @created 2017-07-10 17:37.
18 | */
19 | @RestController
20 | public class UserController {
21 |
22 | @Autowired
23 | private UserService userInfoService;
24 |
25 | @RequestMapping("/hello")
26 | public String hello(){
27 | return "hello";
28 | }
29 |
30 | @RequestMapping("/user/{name}")
31 | public User findByName(@PathVariable String name){
32 | return userInfoService.findByUserName(name);
33 | }
34 |
35 | @RequestMapping("/user/add")
36 | public int addUser(@Valid User user, BindingResult result){
37 | if(result.hasErrors()){
38 | System.out.println("添加用户异常");
39 | return -1;
40 | }
41 | return userInfoService.save(user);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/chengbinbbs/mapper/UserMapper.java:
--------------------------------------------------------------------------------
1 | package com.chengbinbbs.mapper;
2 |
3 |
4 | import com.chengbinbbs.model.User;
5 |
6 | /**
7 | * @author zhangcb
8 | * @created on 2017/5/15.
9 | */
10 | public interface UserMapper {
11 |
12 | User findByUserName(String name);
13 |
14 | public int insert(User user);
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/chengbinbbs/model/User.java:
--------------------------------------------------------------------------------
1 | package com.chengbinbbs.model;
2 |
3 | import org.hibernate.validator.constraints.NotEmpty;
4 |
5 | import javax.validation.constraints.Max;
6 | import javax.validation.constraints.Min;
7 | import java.io.Serializable;
8 |
9 | /**
10 | * 用户实体类
11 | *
12 | * @author zhangcb
13 | * @created 2017-05-23 10:34.
14 | */
15 | public class User implements Serializable {
16 |
17 | private Long id;
18 |
19 | @NotEmpty(message="姓名不能为空")
20 | private String name;
21 |
22 | @Max(value = 100, message = "年龄不能大于100岁")
23 | @Min(value= 18 ,message= "必须年满18岁!" )
24 | private Integer age;
25 |
26 | public User() {
27 | }
28 |
29 | public User(String name, Integer age) {
30 | this.name = name;
31 | this.age = age;
32 | }
33 |
34 | public Long getId() {
35 | return id;
36 | }
37 |
38 | public void setId(Long id) {
39 | this.id = id;
40 | }
41 |
42 | public String getName() {
43 | return name;
44 | }
45 |
46 | public void setName(String name) {
47 | this.name = name;
48 | }
49 |
50 | public Integer getAge() {
51 | return age;
52 | }
53 |
54 | public void setAge(Integer age) {
55 | this.age = age;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/chengbinbbs/service/UserService.java:
--------------------------------------------------------------------------------
1 | package com.chengbinbbs.service;
2 |
3 | import com.chengbinbbs.mapper.UserMapper;
4 | import com.chengbinbbs.model.User;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.retry.annotation.Backoff;
7 | import org.springframework.retry.annotation.Recover;
8 | import org.springframework.retry.annotation.Retryable;
9 | import org.springframework.stereotype.Service;
10 | import org.springframework.transaction.annotation.Transactional;
11 |
12 | /**
13 | * 用户服务实现
14 | *
15 | * @author zhangcb
16 | * @created 2017-07-10 10:35.
17 | */
18 | @Service("userInfoService")
19 | public class UserService {
20 | int i=1;
21 | @Autowired
22 | private UserMapper userMapper;
23 |
24 | public User findByUserName(String name) {
25 | return userMapper.findByUserName(name);
26 | }
27 |
28 | /**
29 | * @Retryable:标注此注解的方法在发生异常时会进行重试
30 | 参数说明:value:抛出指定异常才会重试
31 | include:和value一样,默认为空,当exclude也为空时,默认所以异常
32 | exclude:指定不处理的异常
33 | maxAttempts:最大重试次数,默认3次
34 | backoff:重试等待策略,默认使用@Backoff,@Backoff的value默认为1000L,multiplier(指定延迟倍数)
35 | 默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为2,则第一次重试为1秒,第二次为
36 | 2秒,第三次为4秒
37 | * @param user
38 | * @return
39 | */
40 | @Retryable(value = {RuntimeException.class},maxAttempts = 4,backoff = @Backoff(delay = 1000l,multiplier = 1))
41 | public int insert(User user) {
42 | userMapper.insert(user);
43 | throw new RuntimeException("插入异常!");
44 | }
45 |
46 | //@Recover:用于@Retryable重试失败后处理方法,此方法里的异常一定要是@Retryable方法里抛出的异常,否则不会调用这个方法
47 | @Recover
48 | public Integer recover(RuntimeException e){
49 | return 6;
50 | }
51 |
52 | @Transactional
53 | public int save(User user) {
54 | userMapper.insert(user);
55 | //模拟事务异常回滚
56 | throw new RuntimeException("插入异常!");
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/chengbinbbs/util/SpringContextHolder.java:
--------------------------------------------------------------------------------
1 | package com.chengbinbbs.util;
2 |
3 | import org.springframework.beans.BeansException;
4 | import org.springframework.beans.factory.NoSuchBeanDefinitionException;
5 | import org.springframework.context.ApplicationContext;
6 | import org.springframework.context.ApplicationContextAware;
7 |
8 | /**
9 | * 用于取得 spring 的 bean 定义.
10 | * 需要在 content-hibernate.xml中加上:
11 | * <bean id="SpringContextUtil " class="SpringContextUtil " scope="singleton" />
12 | * 当对SpringContextUtil 实例时就自动设置applicationContext,以便后来可直接用applicationContext
13 | *
14 | * @author Watson
15 | * @since 4.0
16 | */
17 |
18 | public class SpringContextHolder implements ApplicationContextAware {
19 |
20 | //Spring应用上下文环境
21 | private static ApplicationContext applicationContext;
22 |
23 | /**
24 | * 实现ApplicationContextAware接口的回调方法,设置上下文环境
25 | *
26 | * @param applicationContext
27 | * @throws BeansException
28 | */
29 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
30 | SpringContextHolder.applicationContext = applicationContext;
31 | }
32 |
33 | /**
34 | * @return ApplicationContext
35 | */
36 | public static ApplicationContext getApplicationContext() {
37 | return applicationContext;
38 | }
39 |
40 | /**
41 | * 获取对象
42 | *
43 | * @param name
44 | * @return Object 一个以所给名字注册的bean的实例
45 | * @throws BeansException
46 | */
47 | public static Object getBean(String name) throws BeansException {
48 | return applicationContext.getBean(name);
49 | }
50 |
51 | /**
52 | * 获取类型为requiredType的对象
53 | * 如果bean不能被类型转换,相应的异常将会被抛出(BeanNotOfRequiredTypeException)
54 | *
55 | * @param name bean注册名
56 | * @param requiredType 返回对象类型
57 | * @return Object 返回requiredType类型对象
58 | * @throws BeansException
59 | */
60 | public static T getBean(String name, Class requiredType) throws BeansException {
61 | return applicationContext.getBean(name, requiredType);
62 | }
63 |
64 | /**
65 | * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
66 | *
67 | * @param name
68 | * @return boolean
69 | */
70 | public static boolean containsBean(String name) {
71 | return applicationContext.containsBean(name);
72 | }
73 |
74 | /**
75 | * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。
76 | * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
77 | *
78 | * @param name
79 | * @return boolean
80 | * @throws NoSuchBeanDefinitionException
81 | */
82 | public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
83 | return applicationContext.isSingleton(name);
84 | }
85 |
86 | /**
87 | * @param name
88 | * @return Class 注册对象的类型
89 | * @throws NoSuchBeanDefinitionException
90 | */
91 | public static Class getType(String name) throws NoSuchBeanDefinitionException {
92 | return applicationContext.getType(name);
93 | }
94 |
95 | /**
96 | * 如果给定的bean名字在bean定义中有别名,则返回这些别名
97 | *
98 | * @param name
99 | * @return
100 | * @throws NoSuchBeanDefinitionException
101 | */
102 | public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
103 | return applicationContext.getAliases(name);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 9090
3 | #字符编码 Request 和 Response 强制设为UTF-8
4 | spring:
5 | http:
6 | encoding:
7 | charset: UTF-8
8 | enabled: true
9 | force: true
10 | #日志颜色
11 | output:
12 | ansi:
13 | enabled: ALWAYS
14 | logging:
15 | level:
16 | tk.mybatis: TRACE
17 | #多数据源 1主2从
18 | datasource:
19 | #从库数量
20 | readSize: 2
21 | # 使用druid数据源
22 | type: com.alibaba.druid.pool.DruidDataSource
23 | #主库
24 | write:
25 | url: jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=utf-8
26 | username: root
27 | password: root
28 | driver-class-name: com.mysql.jdbc.Driver
29 | filters: stat
30 | maxActive: 20
31 | initialSize: 1
32 | maxWait: 60000
33 | minIdle: 1
34 | timeBetweenEvictionRunsMillis: 60000
35 | minEvictableIdleTimeMillis: 300000
36 | validationQueryTimeout: 900000
37 | validationQuery: SELECT SYSDATE() from dual
38 | testWhileIdle: true
39 | testOnBorrow: false
40 | testOnReturn: false
41 | poolPreparedStatements: true
42 | maxOpenPreparedStatements: 20
43 | read1:
44 | url: jdbc:mysql://localhost:3306/slave1?useUnicode=true&characterEncoding=utf-8
45 | username: root
46 | password: root
47 | driver-class-name: com.mysql.jdbc.Driver
48 | filters: stat
49 | maxActive: 20
50 | initialSize: 1
51 | maxWait: 60000
52 | minIdle: 1
53 | timeBetweenEvictionRunsMillis: 60000
54 | minEvictableIdleTimeMillis: 300000
55 | validationQueryTimeout: 900000
56 | validationQuery: SELECT SYSDATE() from dual
57 | testWhileIdle: true
58 | testOnBorrow: false
59 | testOnReturn: false
60 | poolPreparedStatements: true
61 | maxOpenPreparedStatements: 20
62 | read2:
63 | url: jdbc:mysql://localhost:3306/slave2?useUnicode=true&characterEncoding=utf-8
64 | username: root
65 | password: root
66 | driver-class-name: com.mysql.jdbc.Driver
67 | filters: stat
68 | maxActive: 20
69 | initialSize: 1
70 | maxWait: 60000
71 | minIdle: 1
72 | timeBetweenEvictionRunsMillis: 60000
73 | minEvictableIdleTimeMillis: 300000
74 | validationQueryTimeout: 900000
75 | validationQuery: SELECT SYSDATE() from dual
76 | testWhileIdle: true
77 | testOnBorrow: false
78 | testOnReturn: false
79 | poolPreparedStatements: true
80 | maxOpenPreparedStatements: 20
81 |
82 | mybatis:
83 | type-aliases-package: com.chengbinbbs.model
84 | mapper-locations: classpath:mapper/*.xml
85 | config-location: classpath:mybatis-config.xml
86 |
87 | pagehelper:
88 | helperDialect: mysql
89 | reasonable: true
90 | supportMethodsArguments: true
91 | params: count=countSql
--------------------------------------------------------------------------------
/src/main/resources/mapper/UserMapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | id, name, age
12 |
13 |
14 |
20 |
21 |
22 | insert into user (id, name, age
23 | )
24 | values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, #{age,jdbcType=INTEGER}
25 | )
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/main/resources/mybatis-config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/main/resources/test.properties:
--------------------------------------------------------------------------------
1 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver
2 | spring.datasource.url=jdbc:mysql://localhost:3306/master
3 | spring.datasource.username=root
4 | spring.datasource.password=root
5 |
6 | spring.datasource.minPoolSize = 3
7 | spring.datasource.maxPoolSize = 25
8 | spring.datasource.maxLifetime = 20000
9 | spring.datasource.borrowConnectionTimeout = 30
10 | spring.datasource.loginTimeout = 30
11 | spring.datasource.maintenanceInterval = 60
12 | spring.datasource.maxIdleTime = 60
13 | spring.datasource.validationQuery = select 1
--------------------------------------------------------------------------------
/src/test/java/com/chengbinbbs/SpringBootDataSourceMutilApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.chengbinbbs;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.springframework.boot.test.context.SpringBootTest;
6 | import org.springframework.test.context.junit4.SpringRunner;
7 |
8 | @RunWith(SpringRunner.class)
9 | @SpringBootTest
10 | public class SpringBootDataSourceMutilApplicationTests {
11 |
12 | @Test
13 | public void contextLoads() {
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/tmlog0.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chengbinbbs/SpringBootDataSourceMutil/94cae723ce8ebad5dbe37d2765437cae8ad293e2/tmlog0.log
--------------------------------------------------------------------------------