zoukankan      html  css  js  c++  java
  • 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

    脱离了数据源的缓存器;

    求大神指教了;如果觉得可以点个推荐;

    觉得不好请手下留情不要点击反对哦,

  • 相关阅读:
    javascript修改浏览器title方法 JS动态修改浏览器标题
    input type="checkbox" 选中传值,不选中传值的方法讲解
    关闭控制台的自动切换按钮
    mac切图
    charles
    apache
    超级经典的HTTP协议讲解
    一个很有趣的算法
    移动端网络判断
    移动端1px细线的处理
  • 原文地址:https://www.cnblogs.com/shizuchengxuyuan/p/6687457.html
Copyright © 2011-2022 走看看