结合java的反射和泛型性质简化JDBC和相应的同步等服务器数据库操作代码
github地址:https://github.com/hzphzp/HeartTrace_Server
我们的服务器端数据库并没有用sqllite, 而是直接用mysql,并且用JDBC直接进行操作,故会出现比较多的sql底层代码,而且我们的数据库中一共有7张表, 如果每一个表都写对应的增加,删除, 更新和查询等操作的话,会有很庞大的代码量。
同时,在我们的设计中,服务器端的数据库功能比较单一,主要是配合同步方案进行设计的。
故,我这里选择使用Java的泛型和反射的机制来进行数据库操作和数据的同步:
数据库操作首先需要Sql语句,这里如果sql语句手写的话,就无法实现所有的表统一操作的目的了,故每一种操作的sql语句, 应该有自己生成的方法, 如下为Update类中的createSql方法代码:
private static String createSql(Class<?> cls, boolean delete){ String str = ""; String sql; int index = 1; for(Field field : cls.getDeclaredFields()){ if(index == 1){ str += "username=?, "; index++; continue; } str += field.getName() + "=?, "; index++; } str = str.substring(0, str.length()-2); if(!delete) { sql = "UPDATE " + cls.getSimpleName() + " SET " + str + " WHERE username = ? AND id = ?"; } else { sql = "UPDATE " + cls.getSimpleName() + "_delete" + " SET " + str + " WHERE username = ? AND id = ?"; } return sql; }
有了sql语句之后, 就是往语句里面的‘?’ 里面填坑了。
这里遇到一个问题,gson将本地传输来的json解析后, Diary里面的一些外键是嵌套形式的,但是服务器端的数据库中只有响应的表的id。
这样填充的时候会出现类型错误。
为了保证操作的统一性, 我在会出现异常的语句中加入了异常的处理,如果有就进行取id操作:
try { pstm.setObject(index, obj); }catch(notserializableexception nse){ field = obj.getClass().getField("id"); int id = field.getInt(obj); pstm.setObject(index, id); }
所以完整的填坑方法是:
package Db; import Json.dbJson.Diary; import java.lang.reflect.Field; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; public class Update { public static<T> boolean update(DatabaseAdapter adapter, T cls, String username, boolean delete){ boolean flag = false; try{ PreparedStatement pstm = adapter.connection.prepareStatement(createSql(cls.getClass(), delete)); int index = 1; for(Field field : cls.getClass().getDeclaredFields()){ if(index == 1){ pstm.setString(index, username); index++; continue; } field.setAccessible(true); Object obj = field.get(cls); setObject(pstm, index, obj); index++; } pstm.setString(index, username); pstm.setObject(index+1, cls.getClass().getField("id").get(cls)); int result = pstm.executeUpdate(); flag = result > 0; }catch(SQLException se){ // 处理 JDBC 错误 se.printStackTrace(); }catch(Exception e){ // 处理 Class.forName 错误 e.printStackTrace(); }finally { return flag; } } static void setObject(PreparedStatement pstm, int index, Object obj) throws NoSuchFieldException, IllegalAccessException, SQLException { Field field; try { pstm.setObject(index, obj); }catch(Exception nse){ field = obj.getClass().getField("id"); int id = field.getInt(obj); pstm.setObject(index, id); } } private static String createSql(Class<?> cls, boolean delete){ String str = ""; String sql; int index = 1; for(Field field : cls.getDeclaredFields()){ if(index == 1){ str += "username=?, "; index++; continue; } str += field.getName() + "=?, "; index++; } str = str.substring(0, str.length()-2); if(!delete) { sql = "UPDATE " + cls.getSimpleName() + " SET " + str + " WHERE username = ? AND id = ?"; } else { sql = "UPDATE " + cls.getSimpleName() + "_delete" + " SET " + str + " WHERE username = ? AND id = ?"; } return sql; } }
响应的,其他几种操作各自写一个类:
接下来就是同步了,具体的同步方案,写在下一个博客中:
Gson gson = new Gson(); //java.lang.reflect.Type classType = new TypeToken<Json.Sync>() {}.getType(); //Json.Sync sync = gson.fromJson(content, classType); Json.Sync sync = gson.fromJson(content, Json.Sync.class); try{ for(Field field : sync.getClass().getDeclaredFields()){ field.setAccessible(true); Object obj = field.get(sync); Type t = field.getGenericType(); if(t instanceof ParameterizedType){ ParameterizedType pt = (ParameterizedType) t; Class clz = (Class) pt.getActualTypeArguments()[0];//List里面的示例的类型 Class clazz = obj.getClass();//List这个类型 Field anchorField = clz.getField("anchor"); Field statusField = clz.getField("status"); Field idField = clz.getField("id"); Method sizeMethod = clazz.getDeclaredMethod("size"); Method getMethod = clazz.getDeclaredMethod("get", int.class); getMethod.setAccessible(true); Method addMethod = clazz.getDeclaredMethod("add", Object.class); addMethod.setAccessible(true); int size = (Integer) sizeMethod.invoke(obj); for(int i = 0; i < size; i++){ Object pattern = getMethod.invoke(obj, i); if(0 == statusField.getInt(pattern)){ //新增的 anchorField.set(pattern, (new Date()).getTime()); Insert.insert(adapter, pattern, username, false); statusField.set(pattern, 9); addMethod.invoke(field.get(syncback), pattern); } else { //不是新增的 Object patternInServer = Search.search(adapter, clz, username, idField.getInt(pattern), false); if(patternInServer == null){ //没找到,在垃圾箱中找找 patternInServer = Search.search(adapter, clz, username, idField.getInt(pattern), true); } if(patternInServer == null){ //在服务器数据库和垃圾箱中都没找到,一定是客户端的代码写错了 anchorField.set(pattern, (new Date()).getTime()); statusField.set(pattern, -1); addMethod.invoke(field.get(syncback), pattern); continue; } if(anchorField.get(pattern).equals(anchorField.get(patternInServer))) { //两个数据之前已经同步好了, 直接利用status进行更新不会发生冲突 if(statusField.getInt(pattern) == -1){ anchorField.set(pattern, (new Date()).getTime()); anchorField.set(patternInServer, anchorField.get(pattern)); Delete.delete(adapter, clz, username, idField.getInt(pattern), false); Insert.insert(adapter, patternInServer, username, true); statusField.set(pattern, -1); addMethod.invoke(field.get(syncback), pattern); } else if(statusField.getInt(pattern) == 1) { anchorField.set(pattern, (new Date()).getTime()); anchorField.set(patternInServer, anchorField.get(pattern)); Update.update(adapter, pattern, username, false); statusField.set(pattern, 9); addMethod.invoke(field.get(syncback), pattern); } else{ //不可能有这种情况,一定是客户端代码写错了 anchorField.set(pattern, (new Date()).getTime()); statusField.set(pattern, -1); addMethod.invoke(field.get(syncback), pattern); } } else { //表示之前本地就没有更新到最新的版本,下面的操作可能存在冲突,目前考虑冲突全部以服务器端优先 if(statusField.getInt(patternInServer) == -1){ //是在垃圾桶中找到这个记录的,证明之前在其他的客户端中对这一项进行了删除,这里对服务器进行更新,同时删除本地 anchorField.set(pattern, (new Date()).getTime()); anchorField.set(patternInServer, anchorField.get(pattern)); Update.update(adapter, pattern, username, true); statusField.set(pattern, -1); addMethod.invoke(field.get(syncback), pattern);//status -1 发回本地,然本地删除 } else { //仍然在服务器端的数据库中,可能出现的冲突时文本的修改,这里以服务器为主 anchorField.set(pattern, (new Date()).getTime()); anchorField.set(patternInServer, anchorField.get(pattern)); statusField.set(patternInServer, 9); addMethod.invoke(field.get(syncback), patternInServer); } } } } } }