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 492794628@qq.com<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 492794628@qq.com<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 492794628@qq.com<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] 執行 org.sqlite.jdbc4.JDBC4PreparedStatement@4bf558aa 添加數據 表: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] 執行 org.sqlite.jdbc4.JDBC4PreparedStatement@2d38eb89 添加數據 表: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] 執行 org.sqlite.jdbc4.JDBC4PreparedStatement@5fa7e7ff 添加數據 表: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] 執行 org.sqlite.jdbc4.JDBC4PreparedStatement@4629104a 添加數據 表: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] 執行 org.sqlite.jdbc4.JDBC4PreparedStatement@27f8302d 添加數據 表: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] 執行 org.sqlite.jdbc4.JDBC4PreparedStatement@4d76f3f8 添加數據 表: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] 執行 org.sqlite.jdbc4.JDBC4PreparedStatement@2d8e6db6 添加數據 表: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] 執行 org.sqlite.jdbc4.JDBC4PreparedStatement@23ab930d 添加數據 表: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] 執行 org.sqlite.jdbc4.JDBC4PreparedStatement@4534b60d 添加數據 表: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] 執行 org.sqlite.jdbc4.JDBC4PreparedStatement@3fa77460 添加數據 表: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 492794628@qq.com<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 492794628@qq.com<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 id=@id", 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 {
111                 log.error("緩存不存在無法修改數據");
112                 throw new UnsupportedOperationException("緩存不存在無法修改數據");
113             }
114         } catch (Exception e) {
115             throw new UnsupportedOperationException("更新數據異常", e);
116         }
117     }
118 
119     /**
120      * 獲取獨占編輯狀態
121      *
122      * @param id
123      * @return
124      */
125     public boolean updateEdit(long id) {
126         CacheBase t = null;
127         t = cacheMap.get(id);
128         if (t == null) {
129             throw new UnsupportedOperationException("未找到數據源");
130         }
131         return updateEdit(t);
132     }
133 
134     /**
135      * 獲取獨占編輯狀態
136      *
137      * @param t
138      * @return
139      */
140     public boolean updateEdit(CacheBase t) {
141         if (t == null) {
142             throw new UnsupportedOperationException("參數 T 為 null");
143         }
144         if (!t.isEdit()) {
145             synchronized (t) {
146                 if (!t.isEdit()) {
147                     /*同步后依然需要雙重判定*/
148                     t.setEdit(true);
149                     return true;
150                 }
151             }
152         }
153         return false;
154     }
155 
156 }

 

可能有人要問, 為啥要加鎖,加版號或者加編輯狀態;

我們先看一張圖片

 當同一份數據,展示給客戶端(web,多線程等)的時候,同時進行獲取,進行編輯,我們不可能每次都需要去調用獨占編輯;

那么問題來了我們就拿modeltest的name字段說明,當前等於123,當client1和client2都表示數據的名字錯誤了需要修改成789;

那么在寫入數據庫的時候總會有先后順序,那么后面的很可能就覆蓋了前面的修改,

我們假如client1先提交,把name字段改為456,這時候client2提交了,789就直接覆蓋了456字段,

程序根本不知道字段的覆蓋了,也不知道哪一個是正確的;

所以我加入了編輯狀態和版號驗證;當然你也可以根據你的需求來進行修改

 1 package net.sz.dbserver.main;
 2 
 3 import net.sz.dbserver.cache.CacheManager;
 4 import net.sz.dbserver.db.DBManager;
 5 import net.sz.dbserver.model.ModelTest;
 6 import net.sz.framework.szlog.SzLogger;
 7 import net.sz.framework.utils.GlobalUtil;
 8 
 9 /**
10  *
11  * <br>
12  * author 失足程序員<br>
13  * blog http://www.cnblogs.com/ty408/<br>
14  * mail 492794628@qq.com<br>
15  * phone 13882122019<br>
16  */
17 public class MainManager {
18 
19     private static SzLogger log = SzLogger.getLogger();
20 
21     public static void main(String[] args) {
22 
23         log.error("創建數據庫,創建數據表結構");
24         DBManager.getInstance().checkTables();
25         /*創建支持id*/
26         GlobalUtil.setServerID(1);
27         ModelTest modelTest = new ModelTest();
28         /*獲取全局唯一id*/
29         modelTest.setId(GlobalUtil.getId());
30         /*設置參數*/
31         modelTest.setName("123");
32 
33         /*創建測試數據先修改數據庫*/
34         try {
35             DBManager.getInstance().getReadDao().insert(modelTest);
36         } catch (Exception e) {
37             log.error("寫入數據失敗", e);
38         }
39 
40         /*打印一次id*/
41         log.error("modelTest.getId()=" + modelTest.getId());
42 
43         for (int i = 0; i < 3; i++) {
44             new Thread(() -> {
45                 try {
46                     /*上面的寫入數據是為了獲取這個id,保證測試代碼編輯功能*/
47                     ModelTest cacheBase = CacheManager.getInstance().getCacheBase(ModelTest.class, modelTest.getId());
48                     if (cacheBase != null) {
49                         log.error("成功獲得數據");
50                         /*獨占編輯狀態你可以不需要*/
51                         if (CacheManager.getInstance().updateEdit(cacheBase)) {
52                             log.error("成功獲得編輯狀態");
53                             /*為了模擬並發,我們采用id,保證唯一的數據查看到底誰寫入成功*/
54                             cacheBase.setName(GlobalUtil.getId() + "");
55                             CacheManager.getInstance().updateCacheBase(cacheBase);
56                             log.error("modelTest.getName()=" + cacheBase.getName());
57                         } else {
58                             log.error("獲取編輯狀態失敗");
59                         }
60                     }
61                 } catch (Exception e) {
62                     log.error("更新數據異常", e);
63                 }
64             }).start();
65         }
66 
67     }
68 
69 }

 

在mainmanager類main函數測試里面加入3個線程模擬並發狀態

 

正常添加的測試數據

 1 [04-07 13:50:50:514:ERROR:MainManager.main():23] 創建數據庫,創建數據表結構
 2 [04-07 13:50:50:937:ERROR:Dao.getColumns():532] 類:net.sz.dbserver.model.ModelTest 字段:log is transient or static or final;
 3 [04-07 13:50:50:952:ERROR:Dao.getColumns():532] 類:net.sz.dbserver.cache.CacheBase 字段:edit is transient or static or final;
 4 [04-07 13:50:50:952:ERROR:Dao.getColumns():532] 類:net.sz.dbserver.cache.CacheBase 字段:versionId is transient or static or final;
 5 [04-07 13:50:50:952:ERROR:Dao.getColumns():532] 類:net.sz.dbserver.cache.CacheBase 字段:createTime is transient or static or final;
 6 [04-07 13:50:50:952:ERROR:Dao.getColumns():532] 類:net.sz.dbserver.cache.CacheBase 字段:lastGetCacheTime is transient or static or final;
 7 [04-07 13:51:37:591:ERROR:MainManager.main():41] modelTest.getId()=7040713505000100000
 8 [04-07 13:51:45:392:ERROR:MainManager.lambda$main$0():49] 成功獲得數據
 9 [04-07 13:51:45:392:ERROR:MainManager.lambda$main$0():49] 成功獲得數據
10 [04-07 13:51:45:392:ERROR:MainManager.lambda$main$0():49] 成功獲得數據
11 [04-07 13:51:45:392:ERROR:MainManager.lambda$main$0():52] 成功獲得編輯狀態
12 [04-07 13:51:45:392:ERROR:MainManager.lambda$main$0():58] 獲取編輯狀態失敗
13 [04-07 13:51:45:392:ERROR:MainManager.lambda$main$0():58] 獲取編輯狀態失敗
14 [04-07 13:51:45:428:ERROR:CacheManager.updateCacheBase():101] 數據已修改,最新版號:2
15 [04-07 13:51:45:428:ERROR:MainManager.lambda$main$0():56] modelTest.getName()=7040713514500100000

修改后的數據;

保證了並發寫入、修改的問題,保證了數據的一致性;

實現滑動緩存

在cache包下面建立里CheckCacheTimer定時器類

 1 package net.sz.dbserver.cache;
 2 
 3 import java.util.HashMap;
 4 import java.util.Map;
 5 import net.sz.framework.szlog.SzLogger;
 6 import net.sz.framework.szthread.TimerTaskModel;
 7 
 8 /**
 9  *
10  * <br>
11  * author 失足程序員<br>
12  * blog http://www.cnblogs.com/ty408/<br>
13  * mail 492794628@qq.com<br>
14  * phone 13882122019<br>
15  */
16 public class CheckCacheTimer extends TimerTaskModel {
17 
18     private static SzLogger log = SzLogger.getLogger();
19 
20     public CheckCacheTimer(int intervalTime) {
21         super(intervalTime);
22     }
23 
24     @Override
25     public void run() {
26         /*考慮緩存的清理的都放在這里、當然有很多值的注意細節有待細化*/
27         HashMap<Long, CacheBase> tmp = new HashMap(CacheManager.getInstance().cacheMap);
28         for (Map.Entry<Long, CacheBase> entry : tmp.entrySet()) {
29             Long key = entry.getKey();
30             CacheBase value = entry.getValue();
31             if (!value.isEdit()) {
32                 /*如果數據不在編輯狀態、且30分鍾無訪問清理*/
33                 if (System.currentTimeMillis() - value.getLastGetCacheTime() > 30 * 60 * 1000) {
34                     synchronized (CacheManager.getInstance().cacheMap) {
35                         if (!value.isEdit()) {
36                             /*如果數據不在編輯狀態、且30分鍾無訪問清理*/
37                             if (System.currentTimeMillis() - value.getLastGetCacheTime() > 30 * 60 * 1000) {
38                                 CacheManager.getInstance().cacheMap.remove(value.getId());
39                             }
40                         }
41                     }
42                 }
43             }
44         }
45     }
46 }

 

在cachemanager類構造函數加入

1     public CacheManager() {
2         /*創建一秒鍾檢查的定時器*/
3         ThreadPool.addTimerTask(ThreadPool.GlobalThread, new CheckCacheTimer(1000));
4     }

 

滑動緩存就構建完成了,

這里就不在測試了,理論就是這么個理論;思路就是這么個思路;

脫離數據源的單純緩存器

改造CacheBase類

  1 package net.sz.net.sz.framework.caches;
  2 
  3 import net.sz.framework.util.AtomInteger;
  4 
  5 /**
  6  *
  7  * <br>
  8  * author 失足程序員<br>
  9  * blog http://www.cnblogs.com/ty408/<br>
 10  * mail 492794628@qq.com<br>
 11  * phone 13882122019<br>
 12  */
 13 public abstract class CacheBase {
 14 
 15     /*編輯狀態 */
 16     private volatile transient boolean edit;
 17     /*版本號 */
 18     private volatile transient AtomInteger versionId;
 19     /*創建時間*/
 20     private volatile transient long createTime;
 21     /*最后獲取緩存時間*/
 22     private volatile transient long lastGetCacheTime;
 23     /*true 表示是滑動緩存*/
 24     private volatile transient boolean slide;
 25     /*清理時間*/
 26     private volatile transient long clearTime;
 27 
 28     public CacheBase() {
 29     }
 30 
 31     /**
 32      * 創建
 33      * @param slide
 34      * @param clearTime
 35      */
 36     protected CacheBase(boolean slide, long clearTime) {
 37         this.slide                      = slide;
 38         this.clearTime                  = clearTime;
 39     }
 40 
 41     /**
 42      *
 43      */
 44     protected void createCache(){
 45         this.edit                       = false;
 46         this.versionId                  = new AtomInteger(1);
 47         this.createTime                 = System.currentTimeMillis();
 48         this.lastGetCacheTime           = System.currentTimeMillis();
 49     }
 50 
 51         /**
 52      *
 53      */
 54     protected void copyCache(CacheBase tmp){
 55         this.edit                       = tmp.edit;
 56         this.versionId                  = tmp.getVersionId();
 57         this.createTime                 = tmp.getClearTime();
 58         this.lastGetCacheTime           = System.currentTimeMillis();
 59     }
 60 
 61     public boolean isEdit() {
 62         return edit;
 63     }
 64 
 65     public void setEdit(boolean edit) {
 66         this.edit = edit;
 67     }
 68 
 69     public AtomInteger getVersionId() {
 70         return versionId;
 71     }
 72 
 73     public void setVersionId(AtomInteger versionId) {
 74         this.versionId = versionId;
 75     }
 76 
 77     public long getCreateTime() {
 78         return createTime;
 79     }
 80 
 81     public void setCreateTime(long createTime) {
 82         this.createTime = createTime;
 83     }
 84 
 85     public long getLastGetCacheTime() {
 86         return lastGetCacheTime;
 87     }
 88 
 89     public void setLastGetCacheTime(long lastGetCacheTime) {
 90         this.lastGetCacheTime = lastGetCacheTime;
 91     }
 92 
 93     public boolean isSlide() {
 94         return slide;
 95     }
 96 
 97     public void setSlide(boolean slide) {
 98         this.slide = slide;
 99     }
100 
101     public long getClearTime() {
102         return clearTime;
103     }
104 
105     public void setClearTime(long clearTime) {
106         this.clearTime = clearTime;
107     }
108 
109 }
View Code

 

改造CacheManager類

  1 package net.sz.net.sz.framework.caches;
  2 
  3 import java.util.concurrent.ConcurrentHashMap;
  4 import net.sz.framework.szlog.SzLogger;
  5 import net.sz.framework.szthread.ThreadPool;
  6 
  7 /**
  8  *
  9  * <br>
 10  * author 失足程序員<br>
 11  * blog http://www.cnblogs.com/ty408/<br>
 12  * mail 492794628@qq.com<br>
 13  * phone 13882122019<br>
 14  */
 15 public class CacheManager {
 16 
 17     private static SzLogger log = SzLogger.getLogger();
 18 
 19     /*緩存集合*/
 20     final ConcurrentHashMap<String, CacheBase> cacheMap = new ConcurrentHashMap<>();
 21 
 22     public CacheManager() {
 23         /*創建一秒鍾檢查的定時器*/
 24         ThreadPool.addTimerTask(ThreadPool.GlobalThread, new CheckCacheTimer(1000, this));
 25     }
 26 
 27     /**
 28      * 默認30分鍾清理的滑動緩存、如果存在緩存鍵、將不再添加
 29      *
 30      * @param key
 31      * @param object
 32      * @return
 33      */
 34     public boolean addCache(String key, CacheBase object) {
 35         return addCache(key, object, 30 * 60 * 1000);
 36     }
 37 
 38     /**
 39      * 默認滑動緩存、如果存在緩存鍵、將不再添加
 40      *
 41      * @param key
 42      * @param object
 43      * @param clearTime 滑動緩存的清理時間
 44      * @return
 45      */
 46     public boolean addCache(String key, CacheBase object, long clearTime) {
 47         return addCache(key, object, clearTime, true);
 48     }
 49 
 50     /**
 51      * 默認滑動緩存、如果存在緩存鍵、將不再添加
 52      *
 53      * @param key
 54      * @param object
 55      * @param clearTime 清理緩存的間隔時間
 56      * @param isSlide true表示滑動緩存,
 57      * @return
 58      */
 59     public boolean addCache(String key, CacheBase object, long clearTime, boolean isSlide) {
 60         return addCache(key, object, clearTime, isSlide, false);
 61     }
 62 
 63     /**
 64      *
 65      * @param key
 66      * @param object
 67      * @param clearTime 清理緩存的間隔時間
 68      * @param isSlide true表示滑動緩存,
 69      * @param put true 表示強制添加數據集合,及已經存在鍵數據
 70      * @return
 71      */
 72     public boolean addCache(String key, CacheBase object, long clearTime, boolean isSlide, boolean put) {
 73         if (put) {
 74             object.createCache();
 75             cacheMap.put(key, object);
 76             if (log.isDebugEnabled()) {
 77                 log.debug("強制添加緩存鍵=" + key);
 78             }
 79             return true;
 80         } else if (!cacheMap.containsKey(key)) {
 81             /*加入同步操作*/
 82             synchronized (key) {
 83                 if (!cacheMap.containsKey(key)) {
 84                     object.createCache();
 85                     cacheMap.put(key, object);
 86                     return true;
 87                 } else {
 88                     if (log.isDebugEnabled()) {
 89                         log.debug("數據已經添加,不能再次添加");
 90                     }
 91                 }
 92             }
 93         } else {
 94             if (log.isDebugEnabled()) {
 95                 log.debug("數據已經添加,不能再次添加");
 96             }
 97         }
 98         return false;
 99     }
100 
101     public boolean removeCache(String key) {
102         cacheMap.remove(key);
103         return true;
104     }
105 
106     public CacheBase getCache(String key) {
107         return getCache(key, CacheBase.class);
108     }
109 
110     /**
111      * 獲取一條數據,這里我只是測試,提供思路,
112      * <br>
113      * 所以不會去考慮list等情況;
114      * <br>
115      * 需要的話可以自行修改
116      *
117      * @param <T>
118      * @param clazz
119      * @param key
120      * @return
121      */
122     public <T extends CacheBase> T getCache(String key, Class<T> clazz) {
123         CacheBase cacheBase = null;
124         cacheBase = cacheMap.get(key);
125         if (cacheBase != null) {
126             /*更新最后獲取緩存的時間*/
127             cacheBase.setLastGetCacheTime(System.currentTimeMillis());
128         }
129         return (T) cacheBase;
130     }
131 
132     /**
133      * 更新緩存數據同時更新數據庫數據
134      *
135      * @param key
136      * @param object
137      * @return
138      */
139     public boolean updateCacheBase(String key, CacheBase object) {
140         if (object == null) {
141             throw new UnsupportedOperationException("參數 object 為 null");
142         }
143         CacheBase cacheBase = null;
144         cacheBase = cacheMap.get(key);
145         /*理論上,控制得當這里是不可能為空的*/
146         if (cacheBase != null) {
147             /*理論上是能絕對同步的,你也可以稍加修改*/
148             synchronized (key) {
149                 /*驗證編輯狀態和版號,保證寫入數據是絕對正確的*/
150                 if (cacheBase.getVersionId() == object.getVersionId()) {
151                     /*拷貝最新數據操作*/
152                     cacheMap.put(key, object);
153                     /*保證寫入數據庫后進行修改 對版本號進行加一操作*/
154                     object.getVersionId().changeZero(1);
155                     /*設置最新的最后訪問時間*/
156                     object.setLastGetCacheTime(System.currentTimeMillis());
157                     /*修改編輯狀態*/
158                     object.setEdit(false);
159                     if (log.isDebugEnabled()) {
160                         log.debug("數據已修改,最新版號:" + object.getVersionId());
161                     }
162                     return true;
163                 } else {
164                     if (log.isDebugEnabled()) {
165                         log.debug("版本已經修改無法進行更新操作");
166                     }
167                     throw new UnsupportedOperationException("版本已經修改無法進行更新操作");
168                 }
169             }
170         } else {
171             if (log.isDebugEnabled()) {
172                 log.debug("緩存不存在無法修改數據");
173             }
174             throw new UnsupportedOperationException("緩存不存在無法修改數據");
175         }
176     }
177 
178     /**
179      * 獲取獨占編輯狀態
180      *
181      * @param key
182      * @return
183      */
184     public boolean updateEdit(String key) {
185         CacheBase cacheBase = null;
186         cacheBase = cacheMap.get(key);
187         if (cacheBase == null) {
188             throw new UnsupportedOperationException("未找到數據源");
189         }
190         return updateEdit(key, cacheBase);
191     }
192 
193     /**
194      * 獲取獨占編輯狀態
195      *
196      * @param key
197      * @param cacheBase
198      * @return
199      */
200     public boolean updateEdit(String key, CacheBase cacheBase) {
201         if (cacheBase == null) {
202             throw new UnsupportedOperationException("參數 cacheBase 為 null");
203         }
204         if (!cacheBase.isEdit()) {
205             synchronized (key) {
206                 if (!cacheBase.isEdit()) {
207                     /*同步后依然需要雙重判定*/
208                     cacheBase.setEdit(true);
209                     /*設置最新的最后訪問時間*/
210                     cacheBase.setLastGetCacheTime(System.currentTimeMillis());
211                     return true;
212                 }
213             }
214         }
215         return false;
216     }
217 
218 }
View Code

 

改造CheckCacheTimer類

 1 package net.sz.net.sz.framework.caches;
 2 
 3 import java.util.HashMap;
 4 import java.util.Map;
 5 import net.sz.framework.szlog.SzLogger;
 6 import net.sz.framework.szthread.TimerTaskModel;
 7 
 8 /**
 9  *
10  * <br>
11  * author 失足程序員<br>
12  * blog http://www.cnblogs.com/ty408/<br>
13  * mail 492794628@qq.com<br>
14  * phone 13882122019<br>
15  */
16 public class CheckCacheTimer extends TimerTaskModel {
17 
18     private static SzLogger log = SzLogger.getLogger();
19     private final CacheManager cacheManager;
20 
21     public CheckCacheTimer(int intervalTime, CacheManager cacheManager) {
22         super(intervalTime);
23         this.cacheManager = cacheManager;
24     }
25 
26     @Override
27     public void run() {
28         /*考慮緩存的清理的都放在這里、當然有很多值的注意細節有待細化*/
29         HashMap<String, CacheBase> tmp = new HashMap(this.cacheManager.cacheMap);
30         for (Map.Entry<String, CacheBase> entry : tmp.entrySet()) {
31             String key = entry.getKey();
32             CacheBase value = entry.getValue();
33             /*理論上,這里是能夠保證絕對緩存,同步*/
34             if (!value.isEdit()) {
35                 /*滑動緩存清理*/
36                 if (value.isSlide() && System.currentTimeMillis() - value.getLastGetCacheTime() < value.getClearTime()) {
37                     continue;
38                 }
39                 /*固定緩存清理*/
40                 if (!value.isSlide() && System.currentTimeMillis() - value.getCreateTime() < value.getClearTime()) {
41                     continue;
42                 }
43                 this.cacheManager.removeCache(key);
44             }
45         }
46     }
47 }
View Code

 

脫離了數據源的緩存器;

求大神指教了;如果覺得可以點個推薦;

覺得不好請手下留情不要點擊反對哦,

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM