提示:本文章的应用场景,需要从数据库一中的数据每天定时同步到另一个数据库当中,两个数据库表都不同,只有部分可匹配,此处便需要配置双数据源,且使用了 atomikos 进行事务管理
文章目录
前言
提示:双数据源需要在配置文件中配置两个数据库连接信息,且需要两个数据库工具类,用于注册
sqlSessionFactory 与 sqlSessionTemplate
pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <!-- <scope>test</scope>--> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.3</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.14</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.20</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
一、读取配置文件,编写两个数据库的配置类?
两个数据库的配置以及工具类:
application.properties
#启动端口
server.port=9050
##from
spring.datasource.primary.jdbc-url=jdbc:mysql://172.17.13.157:26001/mainTest?useUnicode=true&characterEncoding=utf8&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8
spring.datasource.primary.username=root
spring.datasource.primary.password=123456
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.primary.borrowConnectionTimeout=30
spring.datasource.primary.loginTimeout=30
spring.datasource.primary.maintenanceInterval=60
spring.datasource.primary.maxIdleTime=60
spring.datasource.primary.maxLifetime=20000
spring.datasource.primary.maxPoolSize=25
spring.datasource.primary.minPoolSize=3
spring.datasource.primary.uniqueResourceName= dataSource1
##to
spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8
spring.datasource.secondary.username=root
spring.datasource.secondary.password=123456
spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.secondary.borrowConnectionTimeout=30
spring.datasource.secondary.loginTimeout=30
spring.datasource.secondary.maintenanceInterval=60
spring.datasource.secondary.maxIdleTime=60
spring.datasource.secondary.maxLifetime=20000
spring.datasource.secondary.maxPoolSize=25
spring.datasource.secondary.minPoolSize=3
spring.datasource.secondary.uniqueResourceName= dataSource2
PrimaryConfig
package rms.bohui.db_sync.config;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import com.alibaba.druid.pool.DruidDataSource;
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.Qualifier;
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;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@MapperScan(basePackages = "rms.bohui.db_sync.fromDB.dao", sqlSessionFactoryRef = "sqlSessionFactory1", sqlSessionTemplateRef = "sqlSessionTemplate1")
public class PrimaryConfig {
@Value("${spring.datasource.primary.driver-class-name}")
private String primary_driverClassName;
@Value("${spring.datasource.primary.jdbc-url}")
private String primary_url;
@Value("${spring.datasource.primary.username}")
private String primary_username;
@Value("${spring.datasource.primary.password}")
private String primary_password;
@Value("${spring.datasource.primary.borrowConnectionTimeout}")
private int borrowConnectionTimeout;
@Value("${spring.datasource.primary.loginTimeout}")
private int loginTimeout;
@Value("${spring.datasource.primary.maintenanceInterval}")
private int maintenanceInterval;
@Value("${spring.datasource.primary.maxIdleTime}")
private int maxIdleTime;
@Value("${spring.datasource.primary.maxLifetime}")
private int maxLifetime;
@Value("${spring.datasource.primary.maxPoolSize}")
private int maxPoolSize;
@Value("${spring.datasource.primary.minPoolSize}")
private int minPoolSize;
@Value("${spring.datasource.primary.uniqueResourceName}")
private String uniqueResourceName;
@Primary
@Bean("dataSource1")
public DataSource primaryDataSource() throws SQLException {
MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
mysqlXADataSource.setUrl(primary_url);
mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXADataSource.setPassword(primary_password);
mysqlXADataSource.setUser(primary_username);
//注册到全局事务
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXADataSource);
xaDataSource.setUniqueResourceName(uniqueResourceName);
xaDataSource.setMinPoolSize(minPoolSize);
xaDataSource.setMaxPoolSize(maxPoolSize);
xaDataSource.setMaxLifetime(maxLifetime);
xaDataSource.setBorrowConnectionTimeout(borrowConnectionTimeout);
xaDataSource.setLoginTimeout(loginTimeout);
xaDataSource.setMaintenanceInterval(maintenanceInterval);
xaDataSource.setMaxIdleTime(maxIdleTime);
xaDataSource.setTestQuery(null);
return xaDataSource;
// DruidDataSource dataSource = new DruidDataSource();
// dataSource.setDriverClassName(this.primary_driverClassName);
// dataSource.setUrl(this.primary_url);
// dataSource.setUsername(this.primary_username);
// dataSource.setPassword(this.primary_password);
// return dataSource;
}
@Primary
@Bean("sqlSessionFactory1")
public SqlSessionFactory sqlSessionFactoryPrimary(@Qualifier("dataSource1")DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
@Bean("sqlSessionTemplate1")
public SqlSessionTemplate sqlSessionTemplatePrimary(@Qualifier("sqlSessionFactory1")SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
配置类当中 @MapperScan 指定了各自数据库操作的dao层代码,所以操作数据库的Mapper代码不可放在一个文件夹当中,平常配置中,该文件应该创建事务管理器,而本项目中使用了其他事务管理,也就是代码中的 AtomikosDataSourceBean 类,所以不需要配置事务管理。
SecondaryConfig
package rms.bohui.db_sync.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
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.Qualifier;
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 javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@MapperScan(basePackages = "rms.bohui.db_sync.toDB.dao", sqlSessionFactoryRef = "sqlSessionFactory2", sqlSessionTemplateRef = "sqlSessionTemplate2")
public class SecondaryConfig {
@Value("${spring.datasource.secondary.driver-class-name}")
private String secondary_driverClassName;
@Value("${spring.datasource.secondary.jdbc-url}")
private String secondary_url;
@Value("${spring.datasource.secondary.username}")
private String secondary_username;
@Value("${spring.datasource.secondary.password}")
private String secondary_password;
@Value("${spring.datasource.secondary.borrowConnectionTimeout}")
private int borrowConnectionTimeout;
@Value("${spring.datasource.secondary.loginTimeout}")
private int loginTimeout;
@Value("${spring.datasource.secondary.maintenanceInterval}")
private int maintenanceInterval;
@Value("${spring.datasource.secondary.maxIdleTime}")
private int maxIdleTime;
@Value("${spring.datasource.secondary.maxLifetime}")
private int maxLifetime;
@Value("${spring.datasource.secondary.maxPoolSize}")
private int maxPoolSize;
@Value("${spring.datasource.secondary.minPoolSize}")
private int minPoolSize;
@Value("${spring.datasource.secondary.uniqueResourceName}")
private String uniqueResourceName;
@Bean("dataSource2")
public DataSource secondaryDataSource() throws SQLException {
MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
mysqlXADataSource.setUrl(secondary_url);
mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXADataSource.setPassword(secondary_password);
mysqlXADataSource.setUser(secondary_username);
//注册到全局事务
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXADataSource);
xaDataSource.setUniqueResourceName(uniqueResourceName);
xaDataSource.setMinPoolSize(minPoolSize);
xaDataSource.setMaxPoolSize(maxPoolSize);
xaDataSource.setMaxLifetime(maxLifetime);
xaDataSource.setBorrowConnectionTimeout(borrowConnectionTimeout);
xaDataSource.setLoginTimeout(loginTimeout);
xaDataSource.setMaintenanceInterval(maintenanceInterval);
xaDataSource.setMaxIdleTime(maxIdleTime);
xaDataSource.setTestQuery(null);
return xaDataSource;
// DruidDataSource dataSource = new DruidDataSource();
// dataSource.setDriverClassName(this.secondary_driverClassName);
// dataSource.setUrl(this.secondary_url);
// dataSource.setUsername(this.secondary_username);
// dataSource.setPassword(this.secondary_password);
// return dataSource;
}
@Bean("sqlSessionFactory2")
public SqlSessionFactory sqlSessionFactorySecondary(@Qualifier("dataSource2") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
@Bean("sqlSessionTemplate2")
public SqlSessionTemplate sqlSessionTemplateSecondary(@Qualifier("sqlSessionFactory2") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
二、编写业务代码
service层代码:
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import rms.bohui.db_sync.config.idgenerate.IdGenerate;
import rms.bohui.db_sync.entity.*;
import rms.bohui.db_sync.fromDB.dao.PrimaryMapper;
import rms.bohui.db_sync.service.Service;
import rms.bohui.db_sync.toDB.dao.SecondaryMapper;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class ServiceImpl implements Service {
static Logger logger= LoggerFactory.getLogger(ServiceImpl.class);
@Autowired
PrimaryMapper primaryMapper;
@Autowired
SecondaryMapper secondaryMapper;
//查询台站相关信息,并进行入库处理
@Transactional(rollbackFor = Exception.class)
@Override
public int syncOrg() {
AtomicInteger successNum = new AtomicInteger(0);
try {
//获取未提取的台站数据
List<TbBaseOrg> orgList = primaryMapper.selectOrg();
orgList.stream().forEach(org->{
org.setDescription("该台站从其他数据库共享而来");
//插入rms数据库
int num = secondaryMapper.insertOrg(org);
if (num>0){
successNum.addAndGet(primaryMapper.updateOrg(org.getStID()));
}
});
}catch (Exception e){
logger.error("同步台站失败,异常:"+e.getMessage());
e.printStackTrace();
}
return successNum.intValue();
}
}
dao层代码:
PrimaryMapper:
package rms.bohui.db_sync.fromDB.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import rms.bohui.db_sync.entity.*;
import java.util.List;
@Mapper
public interface PrimaryMapper {
//查询未同步的台站信息
@Select({"<script>",
"select * from tb_base_station t where t.fetch_flag_SYS_ZHYW = 0",
"</script>"})
List<TbBaseOrg> selectOrg();
//修改台站数据提取标识
@Update({"update tb_base_station t set t.fetch_flag_SYS_ZHYW = 1 , t.fetch_time_SYS_ZHYW = sysdate()",
" where stID = #{id} "})
int updateOrg(int id);
}
SecondaryMapper:
import org.apache.ibatis.annotations.*;
import rms.bohui.db_sync.entity.*;
import java.util.List;
@Mapper
public interface SecondaryMapper {
/**
* 新增 单位
* @param rmsSysOrg 单位 数据实体
* @return 影响行数
*/
@Insert({"<script>",
"INSERT INTO tb_system_org (",
"<trim suffixOverrides=','>",
" is_valid,",
" creator_id,",
" create_time,",
" <if test='stName != null'> name,</if>",
" <if test='stType != null'> type_dic,</if>",
" <if test='phone != null'> telephone,</if>",
" <if test='addr != null'> address,</if>",
" <if test='longitude != null'> longitude,</if>",
" <if test='lititude != null'> latitude,</if>",
" <if test='altitude != null'> altitude,</if>",
" <if test='acreage != null'> land_area,</if>",
" <if test='build_time != null'> build_time,</if>",
" <if test='stCode != null'> org_code,</if>",
" <if test='description != null'> description,</if>",
" <if test='stID != null'> fetch_id,</if>",
"</trim>",
") ",
"VALUES (",
"<trim suffixOverrides=','>",
" 0, ",
" 1, ",
" sysdate(),",
" <if test='stName != null'> #{stName},</if>",
" <if test='stType != null'> #{stType},</if>",
" <if test='phone != null'> #{phone},</if>",
" <if test='addr != null'> #{addr},</if>",
" <if test='longitude != null'> #{longitude},</if>",
" <if test='lititude != null'> #{lititude},</if>",
" <if test='altitude != null'> #{altitude},</if>",
" <if test='acreage != null'> #{acreage},</if>",
" <if test='build_time != null'> #{build_time},</if>",
" <if test='stCode != null'> #{stCode},</if>",
" <if test='description != null'> #{description},</if>",
" <if test='stID != null'> #{stID},</if>",
"</trim>",
")",
"</script>"})
int insertOrg(TbBaseOrg rmsSysOrg);
}
台站实体类代码:
package rms.bohui.db_sync.entity; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * 台站实体类 */ @Data @NoArgsConstructor public class TbBaseOrg { /** 共享台站表记录ID */ private int stID; /** 最后修改用户ID */ private int userID; /** 台站编号 */ private String stCode; /** 台站名称 */ private String stName; /** 台站类型 */ private String stType; /** 所属机构 */ private String org; /** 台站地址 */ private String addr; /** 联系电话 */ private String phone; /** 建立时间 */ private String build_time; /** 经度 */ private String longitude; /** 纬度 */ private String lititude; /** 海拔 */ private String altitude; /** 占地面积 */ private String acreage; /** 所在城市名称 */ private String city_name; /** 创建时间 */ private String createdTime; /** 发布时间 */ private String modifyTime; /** 提取标识 */ private String fetchFlag; /** 提取时间 */ private Date fetchTime; /** rms数据库主键id */ private Long id; /** 描述 */ private String description; }
其他业务代码不在描述,正常调用service即可。
如需要打印日志到文件中,可加入logback.xml
logback.xml:
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="true"> <!-- 应用名称 --> <property name="APP_NAME" value="rmsSyncLog" /> <!--日志文件的保存路径,首先查找系统属性-Dlog.dir,如果存在就使用其;否则,在当前目录下创建名为logs目录做日志存放的目录 --> <property name="LOG_HOME" value="${log.dir:-logs}/${APP_NAME}" /> <!-- 日志输出格式 --> <property name="ENCODER_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n" /> <contextName>${APP_NAME}</contextName> <!-- 控制台日志:输出全部日志到控制台 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <Pattern>${ENCODER_PATTERN}</Pattern> </encoder> </appender> <!-- 文件日志:输出全部日志到文件 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/output-%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>7</maxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${ENCODER_PATTERN}</pattern> </encoder> </appender> <!-- 错误日志:用于将错误日志输出到独立文件 --> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/error-%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>7</maxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${ENCODER_PATTERN}</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>WARN</level> </filter> </appender> <root> <level value="INFO" /> <appender-ref ref="STDOUT" /> <appender-ref ref="FILE" /> <appender-ref ref="ERROR_FILE" /> </root> </configuration>
总结
提示:代码已经演示完毕,只演示部分代码,其他代码贴出来也没用,业务不适用,不同数据库操作,调用不同的Mapper即可。