前言
前文講述了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 }
然后在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 }
以上代碼我們完成了數據庫文件和數據表的創建
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
重構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 }
改造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 }
改造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 }
脫離了數據源的緩存器;
求大神指教了;如果覺得可以點個推薦;
覺得不好請手下留情不要點擊反對哦,