1. 程式人生 > >net.sz.framework 框架 輕鬆搭建資料服務中心----讀寫分離資料一致性,滑動快取

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  * mail 
[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 }
View Code

然後在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 結果 影響行數:1
View 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 {