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.EntityJPA的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&amp;autoReconnectForPools=true&amp;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>