此项目是数据查询接口服务。通过浏览器访问url,传递json参数,返回json数据。用springboot微服务构建,整合mybatis,查询数据库,由于数据保存在两个数据库,所以项目中通过service实现类所在包或者通过自定义注解进行动态的切换数据源。配置如下
1. 引入依赖
<groupId>com.lancygroupId>
<artifactId>interfaces-serviceartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>jarpackaging>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>1.4.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>1.4.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<version>1.4.2.RELEASEversion>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
<version>1.4.2.RELEASEversion>
dependency>
<dependency>
<groupId>com.oraclegroupId>
<artifactId>ojdbc6artifactId>
<version>11.2.0.1.0version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.1.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.0.16version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>4.3.4.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>4.3.4.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.8.9version>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>4.1.6version>
dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>1.4.2.RELEASEversion>
<configuration>
<fork>truefork>
<mainClass>com.lancy.interfaces.InterfacesApplicationmainClass>
configuration>
<executions>
<execution>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
2. springboot启动入口类配置
package com.lingnanpass.interfaces;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = { "com.lancy.interfaces.common", "com.lancy.interfaces.*.dao",
"com.lancy.interfaces.*.service.impl", "com.lancy.interfaces.controller",
"com.lancy.interfaces.util" })
//@ComponentScan告诉Spring 哪些路径下的类(类上有注解) 用自动扫描注入的方式装入spring容器供spring管理生命周期。
public class InterfacesApplication {
public static void main(String[] args) {
SpringApplication.run(LntInterfacesApplication.class, args);
}
}
3. 多数据源配置
3.1 application.properties 配置
springboot核心配置文件是指在resources根目录下的application.properties或application.yml配置文件,这里用properties配置文件,读取properties配置文件的方法有两种。一种是@Value(”${}”)。一种是@Autowired private Environment env; env.getProperty(“test.msg”);
#数据源1--自动切换数据源的包:lnt
jdbc1.db.type=oracle
jdbc1.driver = oracle.jdbc.driver.OracleDriver
jdbc1.url=jdbc:oracle:thin:@192.168.1.203:1521/test1
jdbc1.user=lancy
jdbc1.pass=123456
jdbc1.maxPoolSize=10
jdbc1.minPoolSize=2
jdbc1.initialPoolSize =2
jdbc1.maxWaitTime =60000
jdbc1.checkIdlePoolTime = 60000
jdbc1.minConnTime = 300000
jdbc1.filters=stat,log4j
#数据源2--自动切换数据源的包:ykt
jdbc2.db.type=oracle
jdbc2.driver = oracle.jdbc.driver.OracleDriver
jdbc2.url=jdbc:oracle:thin:@192.168.1.205:1521/test2
jdbc2.user=udev_ykt
jdbc2.pass=123456
jdbc2.maxPoolSize=10
jdbc2.minPoolSize=2
jdbc2.initialPoolSize =2
jdbc2.maxWaitTime =60000
jdbc2.checkIdlePoolTime = 60000
jdbc2.minConnTime = 300000
jdbc2.filters=stat,log4j
oracle.typeAliasesPackage=com.lancy.interfaces.*.entity
oracle.mapperLocations=com/lancy/interfaces/*/dao/oracle/impl*/*.xml
#以下为配置连接池监控界面的信息allow,允许访问监控的IP,用户名,密码,是否运行充值数据
oracle.druidAllow=10.230.0.1/255,192.168.1.100/255
oracle.druidUsername=admin
oracle.druidPassword=123456
oracle.druidResetEnable=false
3.2 动态数据源配置
首先定义一个类,用于保存当前线程使用的数据源名
package com.lancy.interfaces.common;
/**
* @ClassName: DataSourceTypeManager.java
* @Description:
*/
public class DataSourceTypeManager {
private static final ThreadLocal dataSourceTypes = new ThreadLocal(){
@Override
protected String initialValue(){
return DataSourceRounting.defaultDataSourceKey;
}
};
public static String get(){
return dataSourceTypes.get();
}
public static void set(String dataSourceType){
dataSourceTypes.set(dataSourceType);
}
public static void reset(){
dataSourceTypes.set(DataSourceRounting.defaultDataSourceKey);
}
}
然后自定义一个javax.sql.DataSource接口的实现,这里只需要继承Spring为我们预先实现好的父类AbstractRoutingDataSource即可:
package com.lancy.interfaces.common;
import java.util.Map;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 多数据源路适配
* @ClassName: DataSourceRounting.java
* @Description: 多数据源路适配,由配置中多个数据源组成,动态按包或自确定注解数据源进行切换
*/
public class DataSourceRounting extends AbstractRoutingDataSource {
public static String defaultDataSourceKey;//默认的数据源
public static Map
然后编写AOP切面,实现数据源切换逻辑。这里有一个问题
一般事务都会在service层添加,如果使用spring的声明式事务管理,在调用service层代码之前,spring会通过aop的方式动态添加事务控制代码,所以如果要想保证事务是有效的,那么就必须在spring添加事务之前把数据源动态切换过来,也就是动态切换数据源的aop要至少在service上添加,而且要在spring声明式事物aop之前添加.
package com.lancy.interfaces.common;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 数据源动态切换,aop实现
*
* @ClassName: DataSourceAdvice.java
* @Description:数据源动态切换,在调用相关子包下的serviceImpl时自动执行
*/
@Aspect
@Component
public class DataSourceAdvice {
//切入点,调用serviceImpl类就会触发
@Pointcut("execution(* com.lancy..service.impl..*(..))")
public void aspect() {
}
@AfterReturning("aspect()")
public void afterReturning() throws Throwable {
DataSourceTypeManager.reset();//把数据源重置回默认
}
@Before("aspect()")
public void before(JoinPoint joinPoint) throws Throwable {
Object target = joinPoint.getTarget();
// 非业务包下不切换数据源,判断两次,防止代理对象没有切换数据源
String cls = target.toString().toLowerCase();
if (!cls.startsWith("com.lancy.interfaces.")) {
cls = target.getClass().getName().toLowerCase();
}
if (!cls.startsWith("com.lancy.interfaces.")) {
return;
}
String className = cls.replace("com.lancy.interfaces.", "");
String packageName = className.substring(0, className.indexOf("."));
//packageName 有lnt 或者ykt 。因为这两个包下dao,service,mapper就是对应一种数据源,所以这里也可以通过包名切换数据源
// 手动数据源
String dsName = "";
//如果类上有DataSource注解,则数据源字符串就是注解上标注的数据源字符串
if (target.getClass().isAnnotationPresent(DataSource.class)) {
DataSource datasource = target.getClass().getAnnotation(DataSource.class);
dsName = datasource.value();
}
//如果注解上的数据源字符串在系统保存的数据源集合里
if (DataSourceRounting.targetDataSources.containsKey(dsName)) {
// 当前线程按指定数据源
DataSourceTypeManager.set(dsName);
} else if (DataSourceRounting.targetDataSources.containsKey(packageName)) {
DataSourceTypeManager.set(packageName);
} else {
DataSourceTypeManager.set(DataSourceRounting.defaultDataSourceKey);
}
}
}
定义数据源注解
package com.lancy.interfaces.common;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 指定数据源注解
*
* @ClassName: DataSource.java
* @Description: 可以注解服务类(service层)或方法,注解类表示改类整个类都使用同一个数据源
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface DataSource {
// 默认按类名自动设置
String value();
// 定义静态数据源
public static final String LNT = "lnt";
public static final String YKT = "ykt";
}
3.3 springboot集成mybatis 实现
这里把数据源注入和sqlSessionFactory注入都放在一起。
@Configuration可理解为用spring的时候xml里面的标签
@Bean可理解为用spring的时候xml里面的标签
package com.lancy.interfaces.common;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import com.alibaba.druid.pool.DruidDataSource;
import com.github.pagehelper.PageHelper;
/**
* springboot集成mybatis的基本步骤 1)创建数据源 2)创建SqlSessionFactory
*/
/**
* @ClassName: OracleMybatisConfig
* @Description: Oracle数据源与mybatis集成类
*/
@Configuration
@MapperScan(basePackages = "com.lancy.interfaces.*.dao")
//MapperScan配置我们需要扫描的Mapper位置
public class OracleMybatisConfig {
@Autowired
private Environment env;
/**
* @Desctiption 创建数据库数据源
* @return DataSource 数据源
*/
@Bean
public DataSource lntDataSource() throws Exception {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(env.getProperty("jdbc1.driver"));
druidDataSource.setUrl(env.getProperty("jdbc1.url"));
druidDataSource.setUsername(env.getProperty("jdbc1.user"));
druidDataSource.setPassword(env.getProperty("jdbc1.pass"));
druidDataSource.setMaxActive(Integer.parseInt(env.getProperty("jdbc1.maxPoolSize")));
druidDataSource.setInitialSize(Integer.parseInt(env.getProperty("jdbc1.initialPoolSize")));
druidDataSource.setMaxWait(Integer.parseInt(env.getProperty("jdbc1.maxPoolSize")));
druidDataSource.setMinIdle(Integer.parseInt(env.getProperty("jdbc1.minPoolSize")));
druidDataSource.setRemoveAbandoned(true);
druidDataSource.setRemoveAbandonedTimeout(60);
druidDataSource.setLogAbandoned(true);
druidDataSource.setTestWhileIdle(true);
druidDataSource.setFilters(env.getProperty("jdbc1.filters"));
druidDataSource.setValidationQuery(env.getProperty("oracle.validationQuery"));
return druidDataSource;
}
/**
* @Desctiption 创建数据库数据源
* @return DataSource 数据源
*/
@Bean
public DataSource yktDataSource() throws Exception {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(env.getProperty("jdbc2.driver"));
druidDataSource.setUrl(env.getProperty("jdbc2.url"));
druidDataSource.setUsername(env.getProperty("jdbc2.user"));
druidDataSource.setPassword(env.getProperty("jdbc2.pass"));
druidDataSource.setMaxActive(Integer.parseInt(env.getProperty("jdbc2.maxPoolSize")));
druidDataSource.setInitialSize(Integer.parseInt(env.getProperty("jdbc2.initialPoolSize")));
druidDataSource.setMaxWait(Integer.parseInt(env.getProperty("jdbc2.maxPoolSize")));
druidDataSource.setMinIdle(Integer.parseInt(env.getProperty("jdbc2.minPoolSize")));
druidDataSource.setRemoveAbandoned(true);
druidDataSource.setRemoveAbandonedTimeout(60);
druidDataSource.setLogAbandoned(true);
druidDataSource.setTestWhileIdle(true);
druidDataSource.setFilters(env.getProperty("jdbc2.filters"));
druidDataSource.setValidationQuery(env.getProperty("oracle.validationQuery"));
return druidDataSource;
}
/**
* @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
* @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
*/
@Bean
@Primary
public DataSourceRounting dynamicDataSource(@Qualifier("lntDataSource") DataSource lntDataSource,
@Qualifier("yktDataSource") DataSource yktDataSource) {
Map targetDataSources = new HashMap();
targetDataSources.put("lnt", lntDataSource);
targetDataSources.put("ykt", yktDataSource);
DataSourceRounting dynamicDataSource = new DataSourceRounting();
dynamicDataSource.setDefaultDataSourceKey("lnt");
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
/**
* @Desctiption 根据数据源创建Mybatis的SqlSessionFactory
* @return SqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("lntDataSource") DataSource lntDataSource,
@Qualifier("yktDataSource") DataSource yktDataSource, PageHelper pageHelper) throws Exception {
SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
fb.setDataSource(this.dynamicDataSource(lntDataSource, yktDataSource));// 指定数据源(这个必须有,否则报错),因为DataSourceRounting是javax.sql.DataSource接口的实现类
// 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
fb.setTypeAliasesPackage(env.getProperty("oracle.typeAliasesPackage"));// 指定基包
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setJdbcTypeForNull(JdbcType.VARCHAR);
configuration.setCallSettersOnNulls(true); // map类型数据空值字段显示
fb.setConfiguration(configuration);
fb.setPlugins(new Interceptor[] { pageHelper });
fb.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources(env.getProperty("oracle.mapperLocations")));// 指定xml文件位置,相当于配置文件里的
return fb.getObject();
}
@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("dialect", "oracle");
pageHelper.setProperties(p);
return pageHelper;
}
}
4. 日志配置
springboot的日志配置默认在resources目录下logback.xml或者logback-spring.xml文件
<configuration scan="false" debug="false">
<property name="LOG_HOME" value="E:/lnt/logs" />
<property name="appName" value="service">property>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
layout>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debuglevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
appender>
<appender name="fileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${appName}.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.logfileNamePattern>
<MaxHistory>365MaxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MBmaxFileSize>
timeBasedFileNamingAndTriggeringPolicy>
rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%npattern>
layout>
<append>trueappend>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>infolevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
appender>
<appender name="fileError" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${appName}-error.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/${appName}-error-%d{yyyy-MM-dd}-%i.logfileNamePattern>
<MaxHistory>365MaxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MBmaxFileSize>
timeBasedFileNamingAndTriggeringPolicy>
rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%npattern>
layout>
<append>trueappend>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>errorlevel>
<onMatch>ACCEPTonMatch>
<onMismatch>DENYonMismatch>
filter>
appender>
<logger name="com.ibatis" level="DEBUG" />
<logger name="com.ibatis.common.jdbc.SimpleDataSource" level="DEBUG" />
<logger name="com.ibatis.common.jdbc.ScriptRunner" level="DEBUG" />
<logger name="com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate" level="DEBUG" />
<logger name="java.sql.Connection" level="DEBUG" />
<logger name="java.sql.Statement" level="DEBUG" />
<logger name="java.sql.PreparedStatement" level="DEBUG" />
<root level="DEBUG">
<appender-ref ref="fileError" />
<appender-ref ref="fileInfo" />
<appender-ref ref="stdout" />
root>
configuration>
到此springboot整合mybatis实现多数据源项目配置就完成了,开发的时候是需要关注业务逻辑,一般的controller如下
@RestController
@RequestMapping(value = "/")
public class CardInfoAction {
@RequestMapping(value = "/test",method = RequestMethod.POST)
@ResponseBody
public Object getCardInfo(@RequestBody String cardId) {
Map resultMap = new HashMap();
resultMap.put("test","success")
return resultMap;
}
}
可参考
可参考