spring-boot mybatis 配置 主从分离 事务
首先是spring-boot的配置 web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>spring.config.name</param-name> <param-value>cpp</param-value> </context-param> <context-param> <param-name>spring.config.path</param-name> <param-value>classpath:config/</param-value> </context-param> <context-param> <param-name>spring.profiles.active</param-name> <param-value>pro</param-value> </context-param> <context-param> <param-name>logging.config</param-name> <param-value>classpath:logbackConfig/logback-me-dev.xml</param-value> </context-param> </web-app>
也可以不采用web.xml,直接在Application.java指向 cpp-pro.yml (baseConfig,MvcConfig,SwaggerConfig,Redisconfig,HttpSessionConfig为其它config配置 跟本文无关) :
@SpringBootApplication //@EnableAutoConfiguration @EnableScheduling public class Application extends SpringBootServletInitializer { public static void main(String[] args) { // TODO Auto-generated method stub //SpringApplication.run(Application.class, args); //System.out.println("classpath:/config/app-dev.yml"); new SpringApplicationBuilder(BaseConfig.class, MvcConfig.class, SwaggerConfig.class, RedisConfig.class, HttpSessionConfig.class,
MyBatisConfig.class ).properties("spring.config.location=classpath:config/cpp-dev.yml").run(args); } }
cpp-pro.yml文件如下:
spring: redis: host: 127.0.0.1 port: 6379 timeout: 0 pool: max-active: -1 #最大连接数 max-idle: -1 #最大空闲数 min-idle: 0 #最小空闲数 max-wait: -1 #连接池耗尽时,新获取连接需要等待的最大时间 freemarker: allow-request-override: false cache: true check-template-location: true charset: UTF-8 content-type: text/html expose-request-attributes: false expose-session-attributes: false expose-spring-macro-helpers: false prefix: #suffix: .ftl suffix: .html template-loader-path: /freemarker/ #request-context-attribute #settings.* #view-names: # whitelist of view names that can be resolved datasource: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/me?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true&useSSL=false username: root password: huhanbo dsslave: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/me?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true&useSSL=false username: root password: huhanbo
mybatis:
typeAliasesPackage: com.me.mybatis.entity
mapperLocations: classpath:mybatisMapper/*.xml
另外 pom.xml的新加如下依赖
<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> <version>5.1.39</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.2</version> </dependency>
书写spring config文件:
package configuration; import java.sql.SQLException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.annotation.Resource; import javax.sql.DataSource; import com.me.common.datasource.DatabaseType; import com.me.common.datasource.DynamicDataSource; import org.apache.ibatis.mapping.DatabaseIdProvider; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer; import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration; import org.mybatis.spring.boot.autoconfigure.MybatisProperties; import org.springframework.beans.factory.ObjectProvider; 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.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.PropertySource; import org.springframework.core.io.ResourceLoader; 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 org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import com.me.property.JdbcProperties; @Configuration @MapperScan(basePackages="com.me.mybatis.persistence") @EnableTransactionManagement public class MyBatisConfig { @Value("${mybatis.mapperLocations}") private String mapperLocations; @Value("${mybatis.typeAliasesPackage}") private String typeAliasesPackage; @Autowired private JdbcProperties jdbcProperties; @Bean @Primary public DataSource masterDataSource() throws Exception { Properties props = new Properties(); props.put("driverClassName", jdbcProperties.getDriverClassName()); props.put("url", jdbcProperties.getUrl()); props.put("username", jdbcProperties.getUsername()); props.put("password", jdbcProperties.getPassword()); return DruidDataSourceFactory.createDataSource(props); // return DataSourceBuilder.create(Thread.currentThread().getContextClassLoader()) // .driverClassName(jdbcProperties.getDriverClassName()).url(jdbcProperties.getUrl()) // .username(jdbcProperties.getUsername()).password(jdbcProperties.getPassword()).build(); } @Bean() public DataSource slaveDataSource() throws Exception { Properties props = new Properties(); props.put("driverClassName", jdbcProperties.getDsslave().getDriverClassName()); props.put("url", jdbcProperties.getDsslave().getUrl()); props.put("username", jdbcProperties.getDsslave().getUsername()); props.put("password", jdbcProperties.getDsslave().getPassword()); return DruidDataSourceFactory.createDataSource(props); } /** * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错 * * */ @Bean public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DatabaseType.masterDatasource, masterDataSource); targetDataSources.put(DatabaseType.slaveDatasource, slaveDataSource); DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法 dataSource.setDefaultTargetDataSource(masterDataSource);// 默认的datasource设置为myTestDbDataSource return dataSource; } /** * 配置事务管理器 */ @Bean public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception { return new DataSourceTransactionManager(dataSource); } @Bean public SqlSessionFactory createSqlSessionFactory(DynamicDataSource dataSource) throws Exception { SqlSessionFactoryBean fb = new SqlSessionFactoryBean(); fb.setDataSource(dataSource); fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations)); fb.setTypeAliasesPackage(typeAliasesPackage); return fb.getObject(); } }
使用web.xml指向cpp-pro.xml的startup class如下 (baseConfig,MvcConfig,SwaggerConfig,Redisconfig,HttpSessionConfig为其它config配置 跟本文无关) :
package startup; import configuration.*; import org.springframework.boot.Banner.Mode; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.support.SpringBootServletInitializer; //@SpringBootApplication //@EnableAutoConfiguration public class Application extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { builder.sources(BaseConfig.class, MyBatisConfig.class, MvcConfig.class, WebSocketConfig.class, FastDfsConfig.class); return builder; } public static void main(String[] args) { // TODO Auto-generated method stub // SpringApplication.run(Application.class, args); SpringApplication app =new SpringApplicationBuilder(BaseConfig.class, MvcConfig.class, SwaggerConfig.class, RedisConfig.class, HttpSessionConfig.class, MyBatisConfig.class); app.setBannerMode(Mode.CONSOLE); app.run(args); } }
事务在需要地方使用 @Transactional 即可
配置只读:
package com.me.common.datasource; /** * 作用: * 1、保存一个线程安全的DatabaseType容器 */ public class DatabaseContextHolder { private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<>(); public static void setDatabaseType(DatabaseType type){ contextHolder.set(type); } public static void clearDatabaseType(){ contextHolder.remove(); } public static DatabaseType getDatabaseType(){ return contextHolder.get(); } }
package com.me.common.datasource; public enum DatabaseType { masterDatasource,slaveDatasource }
package com.me.common.datasource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { protected Object determineCurrentLookupKey() { return DatabaseContextHolder.getDatabaseType(); } }
package com.me.common.datasource; 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 ReadOnlyConnection { }
package com.me.common.datasource; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; @Aspect @Component public class ReadOnlyConnectionInterceptor implements Ordered { private static final Logger logger = LoggerFactory.getLogger(ReadOnlyConnectionInterceptor.class); @Around("@annotation(readOnlyConnection)") public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ReadOnlyConnection readOnlyConnection) throws Throwable { try { logger.info("set database connection to read only"); DatabaseContextHolder.setDatabaseType(DatabaseType.slaveDatasource); Object result = proceedingJoinPoint.proceed(); return result; } finally { DatabaseContextHolder.clearDatabaseType(); logger.info("restore database connection"); } } @Override public int getOrder() { return 0; } }