两个数据库之间的数据同步(学工系统和oa系统信息同步)


问题描述

现有两个数据库,xg系统和oa系统。要实现这两个数据库之间的数据同步。学⼯系统在进⾏新增或者变更时,需要在OA系统中进⾏相应变化;

需要了解的:(jdbc,数据库,线程,连接池,集合)

数据库部分:

  1. 创建表
#新建数据库
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 '质疑时间');

java部分

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<String> filter_keySet = filter.keySet();
			for (Object key : filter_keySet) {
				querySql.append(" and ").append(key).append("=:").append(key);
			}
		}
		
		List<Map> xglist = new CRUD().queryForList(xg,querySql.toString(), filter, 1, 0);
		List<Map> oalist =new CRUD().queryForList(oa,querySql.toString(), filter,1,0);
		List[] compare = utils.ListComparatorUtil.compare(oalist,xglist );
		List<Map> add_result = compare[0];// add
		if(add_result != null && !add_result.isEmpty()){
			//INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
			for (int i = 0; i < add_result.size(); i++) {
				Map map = add_result.get(i);
				Set keySet = map.keySet();
				Map<String, Object> params = new HashMap<String, Object>();
				StringBuffer sql =  new StringBuffer();//"insert into data_xsxx() VALUES()";
				StringBuffer flieds = new StringBuffer();
				StringBuffer values = new StringBuffer();
				for(Object key:keySet){
					flieds.append(",").append(key);
					values.append(",:").append(key);
					params.put(key.toString(), map.get(key));
				}
				sql.append("insert into ").append(tableName).append("(").append(flieds.deleteCharAt(0)).append(")")
					.append(" VALUES(").append(values.deleteCharAt(0)).append(")");
				new CRUD().executeUpdate(xg,sql.toString(), params);
			}
			
		}

		List<Map> update_result = compare[1];// update
		if(update_result != null && !update_result.isEmpty()){
			for (int i = 0; i < update_result.size(); i++) {
				Map map = update_result.get(i);
				Set keySet = map.keySet();
				Map<String, Object> params = new HashMap<String, Object>();
				StringBuffer sql = new StringBuffer();// "insert into data_xsxx() VALUES()";
				StringBuffer flieds = new StringBuffer();
				for (Object key : keySet) {
					flieds.append(",").append(key).append("=:").append(key);
					params.put(key.toString(), map.get(key));
					
				}
				sql.append("UPDATE ").append(tableName).append(" set ").append(flieds.deleteCharAt(0))
						.append(" WHERE id=:id");
				new CRUD().executeUpdate(xg,sql.toString(), params);
			
			}
		}
	}
}

上面用到了一个封装好了的更新和插入方法,如果不考虑sql注入可以写简单一点,但是这里采用了参数化sql语句,封装了preparestatement。

6. 数据库同步方法(核心)

数据库同步需要首先需要通过查询返回一个链表,然后比较两个链表的内容,是要进行更新操作还是插入操作,根据比较方法返回的list<map>拼接生成我们需要的sql语句,调用之前封装好的更新方法实现同步。

这里需要注意比较链表信息调用的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<String, Object> params) throws SQLException {
		PreparedStatement pstat = null;
		List<Object> paramList = new ArrayList<Object>();// 参数列表集合
		List<String> keyList = new ArrayList<String>();//:后面的值
		StringBuffer sqlbak = new StringBuffer();//作为加入:xx为分割后的字符串的容器
		if (params != null && !params.isEmpty()) {
			String para = isPlaceholder(sql, keyList);//判断占位符是否正确
			String[] split = sql.split(para);
			int length = split.length;
			boolean split_last_part_is_space = "".equals(split[length - 1].trim());// sql语句分隔之后的最后部分是否为空白符
			for (int i = 0; i < length; i++) {
				int keyListSize = keyList.size();
				sqlbak.append(split[i]);
				if ((split_last_part_is_space && i == length - 1) || i == keyListSize) {// 当最后部分为空白符时,最后部分就不存在参数占位符
					continue;
				}
				String key = keyList.get(i);
				appendstr(params, paramList, sqlbak, key);
			}
			logger.info("预编译语句:"+sqlbak);
			pstat = pstat_set(conn, pstat, paramList, sqlbak);
		}else{
			pstat = conn.prepareStatement(sql);//没有参数的情况就直接执行预编译
			logger.info(sql);
		}
		return pstat;
	}

	
	/**
	 * 给preparestatement?替换为params(map)的值,预编译结束返回pstat
	 * select * form data_xsxx where id ='x001'
	 * @param conn
	 * @param pstat
	 * @param paramList
	 * @param sqlbak
	 * @return
	 * @throws SQLException
	 */
	private PreparedStatement pstat_set(Connection conn, PreparedStatement pstat, List<Object> paramList,
			StringBuffer sqlbak) throws SQLException {
		if (!paramList.isEmpty()) {
			pstat =conn.prepareStatement(sqlbak.toString(), ResultSet.TYPE_SCROLL_INSENSITIVE,
					ResultSet.CONCUR_READ_ONLY);
			for (int i = 0; i < paramList.size(); i++) {
				Object value = paramList.get(i);
				if (value instanceof java.util.Date) {// 日期格式转换
					java.util.Date dateValue = (java.util.Date) value;
					java.sql.Timestamp timestamp = new Timestamp(dateValue.getTime());
					pstat.setObject(i + 1, timestamp);
				} else {
					pstat.setObject(i + 1, value);//preparestatement将?设成map里面的参数的值
				}
			}
		}
		return pstat;
	}

	
	/**
	 *sqlbak字符串 添加占位符 ?   并将params的值添加到paramList
	 *select * form data_xsxx where id =?  paramlist->[x001]
	 * @param params
	 * @param paramList
	 * @param sqlbak
	 * @param key
	 */
	private void appendstr(Map<String, Object> params, List<Object> paramList, StringBuffer sqlbak, String key) {
		if (params.containsKey(key)) {
			Object object = params.get(key);//参数的值
			if (object instanceof Collection) {// 集合参数
				Collection<?> c = (Collection<?>) object;
				Iterator<?> iterator = c.iterator();
				StringBuffer part = new StringBuffer();
				if (iterator.hasNext()) {
					Object obj = iterator.next();
					paramList.add(obj);
					part.append(",?");
				}
				sqlbak.append(part.substring(1));
			} else if (object instanceof Object[]) {// 数组参数
				Object[] objs = (Object[]) object;
				StringBuffer part = new StringBuffer();
				for (Object obj : objs) {
					paramList.add(obj);
					part.append(",?");
				}
				sqlbak.append(part.substring(1));
			} else {// 单个参数
				paramList.add(object);
				sqlbak.append("?");
			}
		} else {
			logger.info("参数params未找到匹配的占位符变量");
		}
	}
	
	/**
	 * 判断是否是正确的占位符形式(:id)并找到该占位符id 放入keylist
	 * @param sql
	 * @param keyList
	 * @return
	 */
	private String isPlaceholder(String sql, List<String> keyList) {
		String para = ":[[A-Za-z_0-9]]+";
		Pattern p = Pattern.compile(para);
		Matcher m = p.matcher(sql);
		
		while (m.find()) {
			int index = m.start();
			int end = m.end();
			String key = sql.substring(index + 1, end);
			keyList.add(key);
		}
		return para;
	}

	/**
	 * 分页查询数据库信息
	 * @param sql
	 * @param params map形式的参数列表
	 * @param m 从第m条数据开始
	 * @param n 到底n条结束
	 * @return  List<Map<String, Object>>
	 * @throws SQLException
	 */
	
	@SuppressWarnings("unchecked")
	public  List<Map> queryForList(DataBasicSource ds,String sql,Map params,int m,int n) throws SQLException  {
		List<Map> result = new ArrayList<Map>();
		Connection conn =ds.getConnection();
		PreparedStatement pstat = null;
		ResultSet rs = null;
		try {
			pstat = prepareStatement(conn,sql, params);
			pstat.setMaxRows(m+n-1);//版本问题
			rs = pstat.executeQuery();
			if (rs != null) {
				ResultSetMetaData metaData = rs.getMetaData();
				int columnCount = metaData.getColumnCount();
				if (m-1 > 0) {
					rs.absolute(m-1);
				}
				while (rs.next()) {
					Map<String, Object> map = new LinkedHashMap<String, Object>();
					for (int i = 0; i < columnCount; i++) {
						String columnName = metaData.getColumnName(i+1);
						map.put(columnName, rs.getObject(i + 1));
					}
					result.add(map);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			ds.release(conn, pstat, null);
		}
		return result;
	}
	
	
	/**
	 * 更新数据库信息
	 * @param sql
	 * @param params
	 * @return 影响的行数
	 */
	@SuppressWarnings("unchecked")
	public int executeUpdate(DataBasicSource ds,String sql,  Map params) {
		int result = 0;
		Connection conn = null;
		PreparedStatement pstat = null;

		try {
			conn = ds.getConnection();
			pstat = prepareStatement(conn,sql, params);
			result = pstat.executeUpdate();// 真正执行SQL语句的处理代码

		} catch (Exception e) {
			System.err.println(e.getMessage());
		} finally {
			ds.release(conn, pstat, null);
		}
		logger.info("更新影响的条数:"+result);
		return result;
	}
}


7.比较两个map类型的链表中的值是否相同,并保存不同的部分,供更新数据库

public class ListComparatorUtil {

	
	/**
	 * @param lst1 来源数据
	 * @param lst2  本数据源
	 * @return	List[], 数据结构说明:[新增数据集合(ArrayList<Map>),更新数据集合(ArrayList<Map>)]
	 */
	public static final List[] compare(List<Map> lst1,List<Map> lst2) {
		List[] result = new List[2];
		
		ArrayList<Map> add_result = new ArrayList<Map>();//add
		result[0] = add_result;

		ArrayList<Map> update_result = new ArrayList<Map>();//update
		result[1] = update_result;
		
		if(lst1 != null && !lst1.isEmpty()
				&& lst2 != null && !lst2.isEmpty()){
			//将List<Map>转化为Map<String,Map>
			Map<String,Map> map1 = new HashMap<String,Map>();
			for(Map m1:lst1){
				String id = m1.get("id").toString();
				map1.put(id, m1);
			}
			
			Map<String,Map> map2 = new HashMap<String,Map>();
			for(Map m1:lst2){
				String id = m1.get("id").toString();
				map2.put(id, m1);
			}
			
			//map1和map2对比
			Set<String> 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;
	}

}


猜你喜欢

转载自blog.csdn.net/woshisomeone/article/details/80861076