net.sz.framework 框架 輕鬆搭建資料服務中心----讀寫分離資料一致性,滑動快取
前言
前文講述了net.sz.framework 框架的基礎實現功能,本文主講 net.sz.framework.db 和 net.sz.framework.szthread;
net.sz.framework.db 是 net.sz.framework 底層框架下的orm框架,仿照翻譯了hibernate實現功能,雖然不足hibernate強大;但在於其功能實現單一高效和高可控性;
net.sz.framework.szthread 是 net.sz.framework 底層框架下的執行緒控制中心和執行緒池概念;
以上就不在贅述,前面的文章已經將結果了;
敘述
無論你是做何種軟體開發,都離不開資料;
資料一般我們都會有兩個問題一直在腦後徘徊,那就是讀和寫的問題;
一般正常情況下資料我們可能出現的儲存源是資料庫(mysql,sqlserver,sqlite,Nosql等)、檔案資料庫(excel,xml,cvs等)
無論是合作資料格式都只是在意資料的儲存;保證資料不丟失等情況;
那麼我們為了資料的讀取和寫入高效會想盡辦法去處理資料,已達到我們需求範圍類的資料最高效最穩當的方式;
今天我們準備的是 orm框架下面的 SqliteDaoImpl 對 sqlite資料來源 進行測試和程式碼設計;換其他資料來源也是大同小異;
準備工作
新建專案 maven java專案 net.sz.dbserver
我們在專案下面建立model、cache、db、main這幾個包;
然後在 model 包 下面建立 ModelTest 類
1 package net.sz.dbserver.model; 2 3 import javax.persistence.Id; 4 import net.sz.framework.szlog.SzLogger; 5 6 /** 7 * 8 * <br> 9 * author 失足程式設計師<br> 10 * blog http://www.cnblogs.com/ty408/<br> 11 * mailView Code[email protected]<br> 12 * phone 13882122019<br> 13 */ 14 public class ModelTest { 15 16 private static SzLogger log = SzLogger.getLogger(); 17 18 /*主鍵ID*/ 19 @Id 20 private long Id; 21 /*內容*/ 22 private String name; 23 24 25 public ModelTest() { 26 } 27 28 public long getId() { 29 return Id; 30 } 31 32 public void setId(long Id) { 33 this.Id = Id; 34 } 35 36 public String getName() { 37 return name; 38 } 39 40 public void setName(String name) { 41 this.name = name; 42 } 43 44 }
然後在db包下面建立dbmanager類;
1 package net.sz.dbserver.db; 2 3 import java.sql.Connection; 4 import java.util.ArrayList; 5 import net.sz.dbserver.model.ModelTest; 6 import net.sz.framework.db.Dao; 7 import net.sz.framework.db.SqliteDaoImpl; 8 import net.sz.framework.szlog.SzLogger; 9 import net.sz.framework.utils.PackageUtil; 10 11 /** 12 * 13 * <br> 14 * author 失足程式設計師<br> 15 * blog http://www.cnblogs.com/ty408/<br> 16 * mail [email protected]<br> 17 * phone 13882122019<br> 18 */ 19 public class DBManager { 20 21 private static SzLogger log = SzLogger.getLogger(); 22 private static final DBManager IN_ME = new DBManager(); 23 24 public static DBManager getInstance() { 25 return IN_ME; 26 } 27 28 Dao dao = null; 29 30 public DBManager() { 31 try { 32 /*不使用連線池,顯示執行sql語句的資料庫操作*/ 33 this.dao = new SqliteDaoImpl("/home/sqlitedata/testdb.dat", true); 34 } catch (Exception e) { 35 log.error("建立資料庫連線", e); 36 } 37 } 38 39 /** 40 * 檢查並建立資料表結構 41 */ 42 public void checkTables() { 43 /*建立連線,並自動釋放*/ 44 try (Connection con = this.dao.getConnection()) { 45 String packageName = "net.sz.dbserver.model"; 46 /*獲取包下面所有類*/ 47 ArrayList<Class<?>> tables = PackageUtil.getClazzs(packageName); 48 if (tables != null) { 49 for (Class<?> table : tables) { 50 /*檢查是否是需要建立的表*/ 51 if (this.dao.checkClazz(table)) { 52 /*建立表結構*/ 53 this.dao.createTable(con, table); 54 } 55 } 56 } 57 } catch (Exception e) { 58 log.error("建立表拋異常", e); 59 } 60 } 61 62 }
我們在dbmanager類裡面通過SqliteDaoImpl 類建立了sqlite資料庫支援的類似於hibernate的輔助;
在checktables下面會查詢我們專案包下面所有型別,並且建立資料表;如果表存在就更新表結構(sqlite特性,不會更新表結構);
我們在checktables函式下面做到了對連線的複用情況;建立後並自動釋放程式碼
接下來main包裡面建立主函式啟動類
1 package net.sz.dbserver.main; 2 3 import net.sz.dbserver.db.DBManager; 4 import net.sz.framework.szlog.SzLogger; 5 6 /** 7 * 8 * <br> 9 * author 失足程式設計師<br> 10 * blog http://www.cnblogs.com/ty408/<br> 11 * mail [email protected]<br> 12 * phone 13882122019<br> 13 */ 14 public class MainManager { 15 16 private static SzLogger log = SzLogger.getLogger(); 17 18 public static void main(String[] args) { 19 log.error("建立資料庫,建立資料表結構"); 20 DBManager.getInstance().checkTables(); 21 } 22 23 }View Code
以上程式碼我們完成了資料庫檔案和資料表的建立
1 --- exec-maven-plugin:1.2.1:exec (default-cli) @ net.sz.dbserver --- 2 設定系統字符集sun.stdout.encoding:utf-8 3 設定系統字符集sun.stderr.encoding:utf-8 4 日誌級別:DEBUG 5 輸出檔案日誌目錄:../log/sz.log 6 是否輸出控制檯日誌:true 7 是否輸出檔案日誌:true 8 是否使用雙緩衝輸出檔案日誌:true 9 [04-07 10:56:38:198:ERROR:MainManager.main():19] 建立資料庫,建立資料表結構 10 [04-07 10:56:38:521:ERROR:Dao.getColumns():532] 類:net.sz.dbserver.model.ModelTest 欄位:log is transient or static or final; 11 [04-07 10:56:38:538:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 檢查結果: 無此表 12 [04-07 10:56:38:561:ERROR:SqliteDaoImpl.createTable():200] 13 表: 14 create table if not exists `ModelTest` ( 15 `Id` bigint not null primary key, 16 `name` varchar(255) null 17 ); 18 建立完成;
這裡的步驟在之前文章《存在即合理,重複輪子orm java版本》裡面有詳細介紹,不過當前版本和當時文章版本又有更多優化和改進;
準備測試資料
1 /*建立支援id*/ 2 GlobalUtil.setServerID(1); 3 for (int i = 0; i < 10; i++) { 4 ModelTest modelTest = new ModelTest(); 5 /*獲取全域性唯一id*/ 6 modelTest.setId(GlobalUtil.getId()); 7 /*設定引數*/ 8 modelTest.setName("123"); 9 10 try { 11 DBManager.getInstance().getDao().insert(modelTest); 12 } catch (Exception e) { 13 log.error("寫入資料失敗", e); 14 } 15 }
輸出
1 --- exec-maven-plugin:1.2.1:exec (default-cli) @ net.sz.dbserver --- 2 設定系統字符集sun.stdout.encoding:utf-8 3 設定系統字符集sun.stderr.encoding:utf-8 4 日誌級別:DEBUG 5 輸出檔案日誌目錄:../log/sz.log 6 是否輸出控制檯日誌:true 7 是否輸出檔案日誌:true 8 是否使用雙緩衝輸出檔案日誌:true 9 [04-07 11:13:17:904:ERROR:MainManager.main():21] 建立資料庫,建立資料表結構 10 [04-07 11:13:18:203:ERROR:Dao.getColumns():532] 類:net.sz.dbserver.model.ModelTest 欄位:log is transient or static or final; 11 [04-07 11:13:18:215:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 檢查結果: 已存在 12 [04-07 11:13:18:216:ERROR:SqliteDaoImpl.existsColumn():130] 資料庫:null 表:ModelTest 對映資料庫欄位:ModelTest 檢查結果:已存在,將不會修改 13 [04-07 11:13:18:216:ERROR:SqliteDaoImpl.createTable():168] 表:ModelTest 欄位:Id 對映資料庫欄位:Id 存在,將不會修改, 14 [04-07 11:13:18:216:ERROR:SqliteDaoImpl.existsColumn():130] 資料庫:null 表:ModelTest 對映資料庫欄位:ModelTest 檢查結果:已存在,將不會修改 15 [04-07 11:13:18:216:ERROR:SqliteDaoImpl.createTable():168] 表:ModelTest 欄位:name 對映資料庫欄位:name 存在,將不會修改, 16 [04-07 11:13:18:245:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 檢查結果: 已存在 17 [04-07 11:13:18:245:ERROR:Dao.insert():1023] 執行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 新增資料 表:ModelTest 18 [04-07 11:13:18:246:ERROR:Dao.insert():1067] 執行 [email protected] 新增資料 表:ModelTest 結果 影響行數:1 19 [04-07 11:13:18:272:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 檢查結果: 已存在 20 [04-07 11:13:18:272:ERROR:Dao.insert():1023] 執行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 新增資料 表:ModelTest 21 [04-07 11:13:18:273:ERROR:Dao.insert():1067] 執行 [email protected] 新增資料 表:ModelTest 結果 影響行數:1 22 [04-07 11:13:18:295:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 檢查結果: 已存在 23 [04-07 11:13:18:296:ERROR:Dao.insert():1023] 執行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 新增資料 表:ModelTest 24 [04-07 11:13:18:297:ERROR:Dao.insert():1067] 執行 [email protected] 新增資料 表:ModelTest 結果 影響行數:1 25 [04-07 11:13:18:319:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 檢查結果: 已存在 26 [04-07 11:13:18:319:ERROR:Dao.insert():1023] 執行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 新增資料 表:ModelTest 27 [04-07 11:13:18:320:ERROR:Dao.insert():1067] 執行 [email protected] 新增資料 表:ModelTest 結果 影響行數:1 28 [04-07 11:13:18:343:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 檢查結果: 已存在 29 [04-07 11:13:18:343:ERROR:Dao.insert():1023] 執行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 新增資料 表:ModelTest 30 [04-07 11:13:18:344:ERROR:Dao.insert():1067] 執行 [email protected] 新增資料 表:ModelTest 結果 影響行數:1 31 [04-07 11:13:18:368:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 檢查結果: 已存在 32 [04-07 11:13:18:368:ERROR:Dao.insert():1023] 執行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 新增資料 表:ModelTest 33 [04-07 11:13:18:369:ERROR:Dao.insert():1067] 執行 [email protected] 新增資料 表:ModelTest 結果 影響行數:1 34 [04-07 11:13:18:391:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 檢查結果: 已存在 35 [04-07 11:13:18:391:ERROR:Dao.insert():1023] 執行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 新增資料 表:ModelTest 36 [04-07 11:13:18:392:ERROR:Dao.insert():1067] 執行 [email protected] 新增資料 表:ModelTest 結果 影響行數:1 37 [04-07 11:13:18:415:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 檢查結果: 已存在 38 [04-07 11:13:18:415:ERROR:Dao.insert():1023] 執行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 新增資料 表:ModelTest 39 [04-07 11:13:18:416:ERROR:Dao.insert():1067] 執行 [email protected] 新增資料 表:ModelTest 結果 影響行數:1 40 [04-07 11:13:18:438:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 檢查結果: 已存在 41 [04-07 11:13:18:439:ERROR:Dao.insert():1023] 執行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 新增資料 表:ModelTest 42 [04-07 11:13:18:440:ERROR:Dao.insert():1067] 執行 [email protected] 新增資料 表:ModelTest 結果 影響行數:1 43 [04-07 11:13:18:461:ERROR:SqliteDaoImpl.existsTable():110] 表:ModelTest 檢查結果: 已存在 44 [04-07 11:13:18:462:ERROR:Dao.insert():1023] 執行 insert into `ModelTest` (`Id`, `name`) values (?, ? ) 新增資料 表:ModelTest 45 [04-07 11:13:18:463:ERROR:Dao.insert():1067] 執行 [email protected] 新增資料 表:ModelTest 結果 影響行數:1View Code
重構modeltest類
首先在cache包下面建立CacheBase類實現快取的基本引數
1 package net.sz.dbserver.cache; 2 3 import javax.persistence.Id; 4 import net.sz.framework.util.AtomInteger; 5 6 /** 7 * 8 * <br> 9 * author 失足程式設計師<br> 10 * blog http://www.cnblogs.com/ty408/<br> 11 * mail [email protected]<br> 12 * phone 13882122019<br> 13 */ 14 public class CacheBase { 15 16 /*主鍵ID*/ 17 @Id 18 protected long Id; 19 20 /*編輯狀態 是 transient 欄位,不會更新到資料庫的*/ 21 private volatile transient boolean edit; 22 /*版本號 是 transient 欄位,不會更新到資料庫的*/ 23 private volatile transient AtomInteger versionId; 24 /*建立時間*/ 25 private volatile transient long createTime; 26 /*最後獲取快取時間*/ 27 private volatile transient long lastGetCacheTime; 28 29 public CacheBase() { 30 } 31 32 /** 33 * 建立 34 */ 35 public void createCache() { 36 edit = false; 37 versionId = new AtomInteger(1); 38 createTime = System.currentTimeMillis(); 39 lastGetCacheTime = System.currentTimeMillis(); 40 } 41 42 public long getId() { 43 return Id; 44 } 45 46 public void setId(long Id) { 47 this.Id = Id; 48 } 49 50 public boolean isEdit() { 51 return edit; 52 } 53 54 public void setEdit(boolean edit) { 55 this.edit = edit; 56 } 57 58 public AtomInteger getVersionId() { 59 return versionId; 60 } 61 62 public void setVersionId(AtomInteger versionId) { 63 this.versionId = versionId; 64 } 65 66 public long getCreateTime() { 67 return createTime; 68 } 69 70 public void setCreateTime(long createTime) { 71 this.createTime = createTime; 72 } 73 74 public long getLastGetCacheTime() { 75 return lastGetCacheTime; 76 } 77 78 public void setLastGetCacheTime(long lastGetCacheTime) { 79 this.lastGetCacheTime = lastGetCacheTime; 80 } 81 82 /** 83 * 拷貝資料 84 * 85 * @param cacheBase 86 */ 87 public void copy(CacheBase cacheBase) { 88 this.Id = cacheBase.Id; 89 } 90 91 }
在cachebase類中,我建立了copy函式用來賦值新資料的;
通過這個型別,我們可以做到定時快取,滑動快取效果;
增加版號的作用在於,更新操作標識,是否是編輯狀態也是用作更新標識;
於此同時我們把原 ModelTest 唯一鍵、主鍵 id 移動到了 cachebase 父類中
修改modeltest類繼承cachebase;
1 public class ModelTest extends CacheBase
改造一下dbmanager
1 Dao readDao = null; 2 Dao writeDao = null; 3 4 public Dao getReadDao() { 5 return readDao; 6 } 7 8 public Dao getWriteDao() { 9 return writeDao; 10 } 11 12 public DBManager() { 13 try { 14 /*不使用連線池,顯示執行sql語句的資料庫操作*/ 15 this.readDao = new SqliteDaoImpl("/home/sqlitedata/testdb.dat", true); 16 /*不使用連線池,顯示執行sql語句的資料庫操作*/ 17 this.writeDao = new SqliteDaoImpl("/home/sqlitedata/testdb.dat", true); 18 } catch (Exception e) { 19 log.error("建立資料庫連線", e); 20 } 21 }
加入讀取資料庫連線、寫入資料庫連線;
CacheManager
在cache包下面建立cachemanager類;
cachemanager 型別是我們具體和重點思路;
構建了讀取,並加入快取集合;
構建了更新並寫入資料庫;
同時讀取和更新都保證執行緒安全性特點;
1 package net.sz.dbserver.cache; 2 3 import java.util.concurrent.ConcurrentHashMap; 4 import net.sz.dbserver.db.DBManager; 5 import net.sz.framework.szlog.SzLogger; 6 7 /** 8 * 9 * <br> 10 * author 失足程式設計師<br> 11 * blog http://www.cnblogs.com/ty408/<br> 12 * mail [email protected]<br> 13 * phone 13882122019<br> 14 */ 15 public class CacheManager { 16 17 private static SzLogger log = SzLogger.getLogger(); 18 private static final CacheManager IN_ME = new CacheManager(); 19 20 public static CacheManager getInstance() { 21 return IN_ME; 22 } 23 /*快取集合*/ 24 final ConcurrentHashMap<Long, CacheBase> cacheMap = new ConcurrentHashMap<>(); 25 26 /** 27 * 獲取一條資料,這裡我只是測試,提供思路, 28 * <br> 29 * 所以不會去考慮list等情況; 30 * <br> 31 * 需要的話可以自行修改 32 * 33 * @param <T> 34 * @param clazz 35 * @param id 36 * @return 37 */ 38 public <T extends CacheBase> T getCacheBase(Class<T> clazz, long id) { 39 CacheBase cacheBase = null; 40 cacheBase = cacheMap.get(id); 41 if (cacheBase == null) { 42 try { 43 /*先讀取資料庫*/ 44 cacheBase = DBManager.getInstance().getReadDao().getObjectByWhere(clazz, "where [email protected]", id); 45 /*加入同步操作*/ 46 synchronized (cacheMap) { 47 /*這個時候再次讀取快取,防止併發*/ 48 CacheBase tmp = cacheMap.get(id); 49 /*雙重判斷*/ 50 if (tmp == null) { 51 /*建立快取標識*/ 52 cacheBase.createCache(); 53 /*加入快取資訊*/ 54 cacheMap.put(id, cacheBase); 55 } else { 56 cacheBase = tmp; 57 } 58 } 59 } catch (Exception e) { 60 log.error("讀取資料異常", e); 61 } 62 } 63 64 if (cacheBase != null) { 65 /*更新最後獲取快取的時間*/ 66 cacheBase.setLastGetCacheTime(System.currentTimeMillis()); 67 } 68 69 return (T) cacheBase; 70 } 71 72 /** 73 * 更新快取資料同時更新資料庫資料 74 * 75 * @param <T> 76 * @param t 77 * @return 78 */ 79 public <T extends CacheBase> boolean updateCacheBase(T t) { 80 if (t == null) { 81 throw new UnsupportedOperationException("引數 T 為 null"); 82 } 83 try { 84 CacheBase cacheBase = null; 85 cacheBase = cacheMap.get(t.getId()); 86 /*理論上,控制得當這裡是不可能為空的*/ 87 if (cacheBase != null) { 88 /*理論上是能絕對同步的,你也可以稍加修改*/ 89 synchronized (cacheBase) { 90 /*驗證編輯狀態和版號,保證寫入資料是絕對正確的*/ 91 if (cacheBase.isEdit() 92 && cacheBase.getVersionId() == t.getVersionId()) { 93 /*拷貝最新資料操作*/ 94 cacheBase.copy(t); 95 /*寫入資料庫,用不寫入還是同步寫入,看自己需求而一定*/ 96 DBManager.getInstance().getWriteDao().update(cacheBase); 97 /*保證寫入資料庫後進行修改 對版本號進行加一操作*/ 98 cacheBase.getVersionId().changeZero(1); 99 /*設定最新的最後訪問時間*/ 100 cacheBase.setLastGetCacheTime(System.currentTimeMillis()); 101 /*修改編輯狀態*/ 102 cacheBase.setEdit(false); 103 log.error("資料已修改,最新版號:" + cacheBase.getVersionId()); 104 return true; 105 } else { 106 log.error("版本已經修改無法進行更新操作"); 107 throw new UnsupportedOperationException("版本已經修改無法進行更新操作"); 108 } 109 } 110 } else {