问题描述:
现有两个数据库,xg系统和oa系统。要实现这两个数据库之间的数据同步。学⼯系统在进⾏新增或者变更时,需要在OA系统中进⾏相应变化;
需要了解的:(jdbc,数据库,线程,连接池,集合)
#新建数据库
Create database oa;
Create database xg;
#数据库查询语句
drop table if EXISTS data_pjzy;
drop table if EXISTS data_pjgs;
DROP TABLE if EXISTS data_pjsq;
DROP TABLE if EXISTS data_xscj;
DROP TABLE if EXISTS data_xsxx;
#学生信息表
CREATE table data_xsxx(
id VARCHAR(32) PRIMARY KEY COMMENT '主键',
xh VARCHAR(255) COMMENT '学号',
xm VARCHAR(255) COMMENT '姓名',
bj VARCHAR(255) COMMENT '班级',
zy VARCHAR(255) COMMENT'专业',
yx VARCHAR(255) COMMENT '院系',
xb VARCHAR(255) COMMENT '性别',
csrq datetime COMMENT '出生日期',
rxsj datetime COMMENT'入学时间',
mz VARCHAR(255) COMMENT '民族'
);
#学生成绩表
CREATE table data_xscj(
id varchar(32) primary key COMMENT '主键',
xsid varchar(255) COMMENT '学生主键',
xk varchar(255) COMMENT '学科',
cj varchar(255) COMMENT '学科成绩',
lrsj datetime COMMENT '录入时间'
);
#评奖申请表
create table data_pjsq(
id varchar(32) primary key comment '主键',
sqjx VARCHAR(255) comment '申请奖项' ,
xsid VARCHAR(255) comment '学生主键' ,
sqsj datetime comment '申请时间' ,
shzt VARCHAR(4) comment '审核状态 100'
);
#评奖公示表
create table data_pjgs(
id varchar(32) primary key COMMENT '主键',
sqid varchar(255) COMMENT '申请主键',
gsbz varchar(255) COMMENT '公示备注',
gssj datetime COMMENT '申请时间'
);
#评奖质疑表
CREATE TABLE data_pjzy(
id VARCHAR(32) PRIMARY KEY COMMENT '主键',
gsid varchar(255) COMMENT '公示主键',
zysm LONGTEXT COMMENT '质疑说明',
zysj datetime COMMENT '质疑时间');
1.系统流程图
2.连接数据库
应用程序如果直接获取连接,则每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长,这样效率不高,因此采用连接池。数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。从而大大提高对数据库操作的性能。
public class DataBasicSource {
private DataSource ds=null;
static Logger logger=Logger.getLogger(DataBasicSource.class);
/**
* 获取数据源
* @param properties
*/
public DataBasicSource(String properties) {
Properties pro=new Properties();
try {
pro.load(DataBasicSource.class.getClassLoader().getResourceAsStream(properties));
//从工具包里获得dbcp提供的数据源
ds=BasicDataSourceFactory.createDataSource(pro);
logger.info("加载属性文件:"+properties+"成功!");
} catch (Exception e) {
logger.error(e.getMessage());
}
}
public DataBasicSource() {
// TODO Auto-generated constructor stub
}
/**
* 获取连接
* @return Connection
* @throws SQLException
*/
public Connection getConnection() throws SQLException{
return ds.getConnection();
}
public void release(Connection conn,Statement ps,ResultSet rs) {
try {
if(rs!=null) {
rs.close();
rs=null;
}
if(ps!=null) {
ps.close();
ps=null;
}
if(conn!=null) {
conn.close();
conn=null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3.
数据库连接的子类
由于本项目中要对两个数据库进行操作,因此可以建立一个抽象类作为数据库连接的父类;学工数据源和OA数据源继承该类,然后传入对应的属性文件即可获取相应的数据源,从而获取连接。
public class OADataSource extends DataBasicSource {
public OADataSource() {
super("oa_dbcp.properties");
}
}
public class XGDataSource extends DataBasicSource{
public XGDataSource() {
super("xg_dbcp.properties");
}
}
4.
读取配置文件(此处连接数据库用的是最新的jdbc的驱动包,所以与老版本的驱动包名不一样,多了一个cj)
当我们更换数据库,或者数据库参数信息被修改时,我们就需要修改源代码来重新获取连接。更方便的方法是:把数据库的参数信息存放在属性文件中,这样,只要我们修改文件的信息,就可以不用在源代码中进行修改了。这样做的好处就是:
①不用对源代码进行修改,提高了开发效率
②直接修改配置信息即可,这也体现了代码的可扩展性
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/oa?serverTimezone=UTC
username=root
password=123456
initialSize=30
maxActive=50
maxIdle=15
minIdle=5
maxWait=30000
5.线程
由于本项目中,oa数据库可以同步学工数据库,学工数据库也可以同步Oa数据库,所以选择建两个线程类去执行我们的同步数据库代码。分别是oa同步学工线程;学工同步Oa线程。因为同步数据库需要数据源以查询相关的数据库信息,然后通过得到的信息进行同步。因此,我们的线程需要传我们的数据源进行初始化。两个线程唯一不同的就是一个是以oa数据库作为更新模板,另一个是以oa数据库作为需要同步的那个数据库。可以建一个父类,让这两个线程继承该类。
public abstract class AbstractThread extends Observable implements Runnable{
protected Logger logger=Logger.getLogger(DataBasicSource.class.getName());
protected DataBasicSource xg;
protected DataBasicSource oa;
protected Long sleep;
public AbstractThread(DataBasicSource xg, DataBasicSource oa, Long sleep) {
this.xg = xg;
this.oa = oa;
this.sleep = sleep;
}
}
子类,这里就写oa同步学工
public class OAtoXGThread extends AbstractThread{
public OAtoXGThread(DataBasicSource xg, DataBasicSource oa, Long sleep) {
super(xg, oa, sleep);
}
@Override
public void run() {
if (initContext()) {
try {
doBussiness();
} catch (InterruptedException e) {
notifyListener();
logger.error("", e);
}
}
}
public void doBussiness() throws InterruptedException {
boolean isStart = true;
while (isStart) {
// 调用同步表的方法
logger.info(">>>>>>>>>>>>>>>>>>>线程执行——" + this.getClass().getName() + ">>>>>>>>>>>>>>>>>>>>");
try {
syncTableData("data_xsxx", null);
Thread.sleep(5000);
} catch (SQLException e) {
logger.error("", e);
}
}
}
/**
* 同步
*
* @param tablename
* @param filter
* @throws SQLException
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void syncTableData(String tableName, Map filter) throws SQLException {
StringBuffer querySql = new StringBuffer("select * from ").append(tableName).append(" where 1=1 ");
if (filter != null && !filter.isEmpty()) {
Set filter_keySet = filter.keySet();
for (Object key : filter_keySet) {
querySql.append(" and ").append(key).append("=:").append(key);
}
}
List
上面用到了一个封装好了的更新和插入方法,如果不考虑sql注入可以写简单一点,但是这里采用了参数化sql语句,封装了preparestatement。
6. 数据库同步方法(核心)
数据库同步需要首先需要通过查询返回一个链表,然后比较两个链表的内容,是要进行更新操作还是插入操作,根据比较方法返回的list
这里需要注意比较链表信息调用的compare方法中,传递的参数是有顺序的,前面一个参数是被比较的参数,后者是需要进行比较的参数,例如,oa数据库同步xg数据库线程,oa数据库中的学生信息表与xg数据库中的学生信息作比较,第一个参数就应该是oalist;否则就成了xg数据库同步oa数据了。
在这里还可能遇到的问题是,oalist和xglist查出来的数据是一样的(数据库里面是不一样的),原因很可能就是数据库连接出了问题,用的是一个数据库连接,而导致这个的原因是,把数据库连接方法声明成了静态的,就不会每次都从连接池获取一个新的连接了。当然,还有很多别的原因也可能导致这个问题。
public class CRUD {
static Logger logger=Logger.getLogger(DataBasicSource.class);/*实例化日志类*/
/**
* 封装preparedStatement,用map装参数,将参数(:id)替换回‘?’,再通过preparedStatement的set方法用实际值替换掉‘?’
* @param conn
* @param sql
* @param params
* @return
* @throws SQLException
*/
public PreparedStatement prepareStatement(Connection conn,String sql, Map params) throws SQLException {
PreparedStatement pstat = null;
List
public class ListComparatorUtil {
/**
* @param lst1 来源数据
* @param lst2 本数据源
* @return List[], 数据结构说明:[新增数据集合(ArrayList),更新数据集合(ArrayList)]
*/
public static final List[] compare(List lst1,List lst2) {
List[] result = new List[2];
ArrayList add_result = new ArrayList();//add
result[0] = add_result;
ArrayList update_result = new ArrayList();//update
result[1] = update_result;
if(lst1 != null && !lst1.isEmpty()
&& lst2 != null && !lst2.isEmpty()){
//将List转化为Map
Map map1 = new HashMap();
for(Map m1:lst1){
String id = m1.get("id").toString();
map1.put(id, m1);
}
Map map2 = new HashMap();
for(Map m1:lst2){
String id = m1.get("id").toString();
map2.put(id, m1);
}
//map1和map2对比
Set keySet = map1.keySet();
for (String key : keySet) {
Map m1 = map1.get(key);
if(map2.containsKey(key)){//存在数据
Map m2 = map2.get(key);
Set m_keySet = m1.keySet();
boolean isSame = true;
for (Object m_key : m_keySet) {
String o1 = m1.get(m_key)+"";
String o2 = m2.get(m_key)+"";
if(o1.compareTo(o2) != 0){
isSame = false;
break;
}
}
if(!isSame){
update_result.add(m1);
}
System.out.println("m1:"+m1);
}else{//不存在数据,该数据属于新增数据?
add_result.add(m1);
}
}
}else{
if(lst1 != null && !lst1.isEmpty()){
add_result.addAll(lst1);
}
if(lst2 != null && !lst2.isEmpty()){//不合理情况?
}
}
return result;
}
}