Source 组件介绍
Source 主要为数据源提供简易的操作接口,使底层的具体数据源(传统数据库、文件系统、内存数据库、Memcached/Redis缓存)对上层是透明的。其提供两种类型的数据源:DataSource 和 CacheSource。DataSource 为数据库或内存数据库,提供类似JPA、Hibernate的接口与功能。CacheSource 为缓存数据提供类似Memcached、Redis的接口和功能。两者也提供了异步接口(基于远程模式Service)。
DataSource 入门
JPA虽然已经提供了简洁成熟的数据库操作接口,但当数据、业务量很庞大的时候就显得捉襟见肘,与JPA相比,DataSource有以下几个特点:
1、简易的过滤查询接口,但仅支持简单的表关联查询。
2、分布式的主键的分布式自增策略。
3、动态更新变更后的数据库连接参数。
4、读写分离的简易配置。
5、进程间的缓存自动同步。
数据库操作方面常见的是过滤查询操作,JPA规范中的JPQL虽然简化了SQL,但是对于动态产生的过滤条件,使用者还是无法免去组装过滤条件的过程(无论JPQL还是CriteriaQuery), DataSource定义了FilterBean接口可以省略组装条件的过程,FilterNode提供了类似CriteriaQuery的功能,且这两种对象都可以序列化,给远程模式Service提供了基础,微服务架构提倡服务之间尽量降低耦合,因此DataSource仅支持简单的关联查询,复杂的表关联查询或统计应放在数据分析系统中。一个服务通常部署多个进程,若用JPA的缓存则进程之间的缓存无法同步,而DataSource采用SNCP协议即可方便地达到自动同步缓存功能。JPA无法在主数据库异常时动态切换到备份数据库,DataSource会监听persistence.xml文件,当文件配置发生变化时自动切换新的数据库配置。
为了降低学习成本,DataSource重用了JPA里的部分注解与配置文件,使用方法基本相同,与JPA用法唯一区别是注解只能标记于字段,不能标记在方法上。
| 注解类名 | 功能描述 |
|---|---|
| javax.persistence.Cacheable | 标记Entity类是否需要缓存,与JPA用法一致 |
| javax.persistence.Column | 标记字段,只使用其name()、insertable()、updatable()属性 |
| javax.persistence.Entity | JPA的Entity类必须标记为@Entity, 而RedKale不强制要求,所以该注解一般无用 |
| javax.persistence.GeneratedValue | 仅用于标记主键是否为数据库自增长,其内值的两属性废弃 |
| javax.persistence.GenerationType | 被@GeneratedValue的属性引用,在RedKale内不被使用到 |
| javax.persistence.Id | 标记主键字段,与JPA用法一致 |
| javax.persistence.Table | 标记表的别名,与JPA用法一致 |
| javax.persistence.Transient | 标记是否为表对应的字段,与JPA用法一致 |
| 以下是RedKale自定义的注解 | |
| @VirtualEntity | 用于非数据库表对应的Entity类,且仅用于开启缓存模式的DataSource |
| @DistributeGenerator | 标记主键是否采用分布式自增长策略,不能与@GeneratedValue同用 |
| @DistributeTables | 当多个表的主键唯一性公用时需要该注解 |
| @FilterColumn | 用于FilterBean过滤类的字段设置 |
| @FilterJoinColumn | 用于FilterBean过滤类的关联表字段设置 |
| @FilterGroup | 用于FilterBean过滤类的过滤条件分组设置 |
操作数据源主要使用的对象有 DataSource、FilterBean、FilterNode。DataSource 提供的接口分几种系列:
| 系列方法 | 功能描述 |
|---|---|
| insert | 插入数据 |
| delete | 删除数据 |
| update | 更新数据 |
| updateColumnXXX | 更新数据的部分字段 |
| getNumberResult | 统计查询,用于查询字段的总和、最大值、平均值等数据 |
| queryColumnXXX | 单个字段数据查询和字段的统计查询 |
| find | 查找单个对象 |
| queryList | 查询对象的List集合 |
| querySheet | 查询对象的Sheet页式集合 |
| directXXX | 直接运行SQL语句,用于复杂的关联查询与更新 |
以上接口除了directXXX,其他都有等同的异步接口。insert、delete、update接口与JPA同名接口用法一样。DataSource提供了丰富的查询接口,且有独特的翻页查询功能。每以系列的方法主要重载三类: 单个字段过滤、FilterBean过滤和FilterNode过滤。
过滤条件
FilterBean、FilterNode对象用于构造过滤条件。FilterBean可以转化为FilterNode。FilterBean主要用于接收外界构建的过滤条件,而FilterNode为了构建内部的过滤条件且降低过滤条件变化的耦合性。
public class UserBean implements FilterBean {
private int userid;
@FilterColumn(express = FilterExpress.LIKE)
private String username;
private Range age;
public UserBean(int userid, String username, Range age) {
this.userid = userid;
this.username = username;
this.age = age;
}
/** 以下省略getter setter方法 */
}
new UserBean(200001, "redkale", new IntRange(14, 36)) 等价于
FilterNode.create("userid", 200001).and("username", FilterExpress.LIKE, "redkale").and("age", new Range.IntRange(14, 36))
new UserBean(200001, "redkale", new IntRange(14, 36)) 等价于 "WHERE userid = 200001 AND username LIKE '%redkale%' AND age BETWEEN 14 AND 36"
new UserBean(200001, "redkale", null) 等价于 "WHERE userid = 200001 AND username LIKE '%redkale%'"
new UserBean(0, "redkale", null) 等价于 "WHERE username LIKE '%redkale%'"
如上定义UserBean过滤条件,当非数值类字段值为null、字符串值为空、数值类字段值小于@FilterColumn.least()值(least的默认值为1)都不会构建成过滤条件。@FilterColumn.express根据字段的类型有不同的默认值,若字段类型为Collection子类或数组则express默认为FilterExpress.IN;若字段类型为Range的子类则express默认为FilterExpress.BETWEEN;其他类型则express默认为FilterExpress.EQUAL。默认字段之间是AND关系,若想使用OR关系则需要使用@FilterGroup进行标记:
public class UserBean implements FilterBean {
private int userid;
@FilterGroup("[OR]a")
@FilterColumn(express = FilterExpress.LIKE)
private String username;
@FilterGroup("[OR]a")
private Range age;
public UserBean(int userid, String username, Range age) {
this.userid = userid;
this.username = username;
this.age = age;
}
/** 以下省略getter setter方法 */
}
new UserBean(200001, "redkale", new IntRange(14, 36)) 等价于
FilterNode orNode = FilterNode.create("username" , FilterExpress.LIKE, "redkale").or("age", new Range.IntRange(14, 36));
FilterNode node = FilterNode.create("userid", 200001).and(orNode));
new UserBean(200001, "redkale", new IntRange(14, 36)) 等价于 "WHERE userid = 200001 AND (username LIKE '%redkale%' OR age BETWEEN 14 AND 36)"
new UserBean(200001, "redkale", null) 等价于 "WHERE userid = 200001 AND username LIKE '%redkale%'"
new UserBean(0, "redkale", null) 等价于 "WHERE username LIKE '%redkale%'"
source.getNumberResult(User.class, FilterFunc.COUNT, null, new UserBean(0, "redkale", new IntRange(14, 36))).intValue() 等价于
"SELECT COUNT(*) FROM user WHERE username LIKE '%redkale%' AND age BETWEEN 14 AND 36"
如上@FilterGroup 的value 必须是[OR]或者[AND]开头,没有标记@FilterGroup的字段等价于标记了@FilterGroup(value = "[AND]")。[AND]、[OR]后面的字符串为GROUP_NAME,默认的GROUP_NAME为空字符串。如上"[OR]a"可以直接使用"[OR]",有多个[OR]或者[AND]则需要加上不同的NAME。
CacheSource 入门
CacheSource同Memcached类似,像一个带有过期功能地Map容器,存放key-value数据。常见的使用场景就是存放HTTP的Session信息。RedKale把用户会话信息数据当做业务数据处理,而不是接入层的数据。WebSocket的连接态数据也是用CacheSource存储。key为WebSocket的groupid,value为WebSocket服务端节点的IP地址列表。
public class UserService implements Service {
//用户简单信息缓存
private final Map<Integer, UserInfo> users = new ConcurrentHashMap<>();
//使用CacheSource必须要指明泛型
@Resource(name = "usersessions")
protected CacheSource<String, Integer> sessions;
//登录
public RetResult<UserInfo> login(LoginBean bean) { //bean.sessionid 在接入层进行赋值
UserInfo user = null;
// 登陆逻辑 user = ...
users.put(user.getUserid(), user);
sessions.set(600, bean.getSessionid(), user.getUserid()); //session过期时间设置为10分钟
return new RetResult<>(user);
}
//获取当前用户信息
public UserInfo current(String sessionid) { //给HTTP的BaseServlet用
Integer userid = sessions.getAndRefresh(sessionid);
return userid == null ? null : users.get(userid);
}
//注销
public void logout(String sessionid) {
sessions.remove(sessionid);
}
}以上是个简单的范例,用于用户模块存放sessionid。
persistence.xml 配置说明
<!-- 其配置算是标准的JPA配置文件的缩略版 -->
<persistence>
<!-- 系统基本库 -->
<persistence-unit name="demouser">
<!-- 为NONE表示不启动缓存,@Cacheable 失效; 非NONE值(通常用ALL)表示开启缓存。 -->
<shared-cache-mode>NONE</shared-cache-mode>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/dbuser?characterEncoding=utf8"/>
<!--
javax.persistence.jdbc.driver在JPA的值是JDBC驱动,RedKale有所不同,值应该是javax.sql.DataSource的子类。
为了兼容用户习惯,RedKale内置常见JDBC驱动到javax.sql.DataSource的映射关系:
com.mysql.jdbc.Driver —————— com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource
org.mariadb.jdbc.Driver —————— org.mariadb.jdbc.MySQLDataSource
oracle.jdbc.driver.OracleDriver —————— oracle.jdbc.pool.OracleConnectionPoolDataSource
com.microsoft.sqlserver.jdbc.SQLServerDriver —————— com.microsoft.sqlserver.jdbc.SQLServerConnectionPoolDataSource
因此 com.mysql.jdbc.Driver 会被自动转换成 com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource
-->
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="123456"/>
<!--
最大连接数,默认值:CPU数*16
<property name="javax.persistence.connections.limit" value="32"/>
-->
<!--
包含的SQL模板,相当于反向LIKE,不同的JDBC驱动的SQL语句不一样,RedKale内置了MySQL、Oracle、Sqlserver的语句
<property name="javax.persistence.contain.sqltemplate" value="LOCATE(${keystr}, ${column}) > 0"/>
<property name="javax.persistence.notcontain.sqltemplate" value="LOCATE(${keystr}, ${column}) = 0"/>
-->
</properties>
</persistence-unit>
<!-- IM消息库 -->
<persistence-unit name="demoim">
<shared-cache-mode>NONE</shared-cache-mode>
<properties>
<!-- jdbc:mysql://127.0.0.1:3306/dbim?autoReconnect=true&autoReconnectForPools=true&characterEncoding=utf8 -->
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/dbim?characterEncoding=utf8"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="123456"/>
</properties>
</persistence-unit>
</persistence>