# DAF4J **Repository Path**: xzhuit/DAF4J ## Basic Information - **Project Name**: DAF4J - **Description**: Data Access Facade For Java,更简单的持久层结构,为数据访问提供统一接口,以及更简单可读的DSL - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 5 - **Created**: 2020-10-29 - **Last Updated**: 2024-02-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #Data Access Facade for Java (DAF4J) 这是一个针对数据访问的统一接口,提供简便的,带类型检查的数据访问DSL >运行于 Java 1.6 以及更高版本 >引入`daf4j-api.jar`和您需要的`DataAccess`实现即可 >DAF4j针对Scala提供了的简化操作 `update.md`中查看更新日志 ###使用 方式1: 在`git@osc`上下载附件并引入包,以及根据说明引入依赖项(`daf4j-api.jar`本身不需要引入依赖,但`DataAccess`实现会需要,后文有详细说明) 方式2: 使用maven引入 ```xml net.cassite daf4j-api 0.1.1-RELEASE ``` ###使用预览 **Java** | Stream风格 ```java query .from(user) .stream() .filter(user.age.$gt(18).and(user.name.$ne("cass"))) .filter(role.name.$eq("admin")) .sorted(user.id.desc()) .limit(10) .list(); // 查询年龄大于18,姓名不为'cass',角色等于'admin'的用户,根据用户id降序排列,并取前10条记录 ``` **Scala** | SQL风格 ```scala query from user where user.age > 18 & user.name <> "cass" & role.name === "admin" param (orderBy(user.id.desc) top 10) list // 功能与上相同 ``` ###开发这个类库的原因 DAF4J用于简化数据访问。 三层架构中数据层的结构会很大程度上依赖于业务逻辑。很多时候业务层仅仅做一些数据校验和数据类型的转换,剩下就是直接的调用数据层,而数据层有时候也会针对业务层方法写一些“专用”方法。 我认为数据层之所以依赖于业务的根源,是查询/修改**条件**无法完整的从业务中分离出去。 即使是类似`Spring Data JPA`或者`JPA Criteria`,对于条件的处理也略显笨重(`小于(字段, 值)`而不是`字段.小于(值)`,而且代码量略高)。 我想,既然`JPA`支持从方法注入值,那么为何不转变一下思路,把字段替换成封装好,支持各种条件和表达式的类型,而方法签名不变。这样既可以使用已有框架,又可以更轻松的调用数据。 设计了一番后便开始了开发。 ##使用方式 >此处给出一个前瞻,详细使用方式可在Wiki页查看 >此外,在Wiki页提供一篇了教学,在一个简单的RBAC的模型的情景下使用DAF4J 使用需要引入`daf4j-api.jar`(或者使用maven) 本例以`daf4j-ds-jpa.jar`中的`JPQLDataAccess`实现为例 我们会书写一个实体类,现在把实现方式作微小的调整: 我针对`IDEA`写了一个getter和setter的生成器,来完成DAF4J的JavaBean生成,生成代码见仓库根目录`GetterGeneratorForIDEA.txt`和`SetterGeneratorForIDEA.txt` ```java @Entity class User{ public final XInt id = new XInt(this); public final XString name = new XString(this); public final XInt age = new XInt(this); @Id public Integer getId(){ return id.get(); } public String getName(){ return name.get(); } public Integer getAge(){ return age.get(); } public void setId(Integer id){ DataUtils.set(this.id, id); } public void setName(String name){ DataUtils.set(this.name, name); } public void setAge(Integer age){ DataUtils.set(this.age, age); } } ``` 为了使用JPA的功能,还需要获取一个`EntityManager`,然后用它初始化`JPQLDataAccess`和`Query` ```java Query query=new Query(new JPQLDataAccess(entityManager)); ``` 接着就可以开始使用了: ```java User user=new User(); // 列出所有年龄大于18岁的用户 query .from(user) .where(user.age.$gt(18)) .list(); // 以List>的形式列出用户id和用户名 // Map中的键包括 User.id 和 user_name // 结果根据年龄降序排序 query .from(user) .where(user.age.$gt(18)) .param( new QueryParameter().orderBy(user.age.desc()) ) .select( new Focus() .focus(user.id) // 别名为类型简称.字段名 .focus(user.name, "user_name") ); // 计算所有用户年龄平均值 query .from(user) .where(null) .avg(user.age); // 将所有用户的年龄+1 query .from(user) .where(null) .update( user.age.as(user.age.add(1)) ); // 删除所有用户 query .from(user) .where(null) .remove(); ``` 上述查询风格是sql风格,有时候并不操作关系型数据库,或者不喜欢类sql的语法,所以此处还提供了stream风格的查询语句 ```java // 列出所有年龄大于18岁的用户 query .from(user).stream() .filter(user.age.$gt(18)) .list(); // 以List>的形式列出用户id和用户名 // Map中的键包括 User.id 和 user_name // 结果根据年龄降序排序 query .from(user).stream() .filter(user.age.$gt(18)) .sorted(user.age.desc()) .map( new Focus() .focus(user.id) .focus(user.name, "user_name") ) .list(); // 计算所有用户年龄平均值 query .from(user).stream() .mapToInt(user.age) .average(); ``` 以上查询都是带类型检查的。 ##已有的DataAccess实现 现在开发了针对`JPA`标准的DataAccess实现,以及对于**资源**的抽象实现。 针对`JPA`的实现在测试环境中使用Hibernate-EntityManager-4.0,在`HSQLDB`和`MySQL`中测试通过。 >如果你下载了源码,则可以直接运行测试用例,`HSQLDB`将在内存运行 依赖于 * `daf4j-api:0.1.1-RELEASE` * `slf4j-api:1.7.12` * `hibernate-jpa-2.0-api:1.0.1.Final` 当然,日志输出还需要额外的日志系统,比如slf4j-simple/log4j+slf4j-log4j等,JPA实现也需要引入依赖。 您可以自行添加依赖或者使用maven ```xml net.cassite daf4j-ds-jpa 0.1.1-RELEASE ``` ###JPQLDataAccess/JPQLDataSource `JPA`是 JavaEE 体系中的一部分,是数据持久化的标准。所以对其进行了实现。JPA提供了`JPQL`和`Criteria`两种查询方式,但是Criteria方式限制很大,所以采用JPQL查询方式作实现。 关系型数据库可以进行join,group by,having,然而daf4j-api中并没有这些元素。这起源于我曾经[针对SQL简化的思考](http://blog.cassite.net/杂谈/有关SQL的简化的思考)。 1. SQL中,若出现聚合函数,那么出现在select子句中却没有出现在聚合函数中的字段必需出现在group by子句中。 2. 若聚合函数作为条件的一部分,那么必需放在having子句中。 也就是说,大部分情况下,group by和having可以推导出来。 而使用JPA时,join关系已经在实体中定义好了。即使写JPQL时也只会这么写 ```sql select u from User u join u.roles r ... ``` 完全可以自动生成。 不过此处还有一些使用规则。 例如User(用户)和Role(角色),如果需要查询用户,也需要以角色为依据。比如说:查询出所有角色为`admin`的用户,那么你必需这么写。 ```java User user=new User(); Role role=new Role(); user.getRoles().add(role); query .from(user) .stream() .filter(role.name.$eq("admin")) .list(); ``` 换句话说,你需要指定join的字段。 如果是多对一关系,则需要通过setter填入。 ####ResourceDataAccess 在0.1.1版本中,api提供了一个新的DataAccess实现。对于所有的“资源”进行抽象。 使用`Resource`对象表示资源,`Source`接口实现表示来源。 API中自带本地文件源`LocalFileSource`实现,使用方式类似如下: Query query=new Query(new ResourceDataAccess(new LocalFileSource)); Resource r = new Resource(); query .from(r) .where(r.location.$like("/Volumes/PROJECTS/openSource/DAF4J")) .list(); `where`子句支持`$eq`,`$ne`,`$lt`,`$gt`,`$le`,`$ge`,`like`。其中必须出现对`location`的`$eq`或`like`。 (like表示从该资源下的子资源中寻找) `select`子句只支持`count` `update`子句支持`location`和`buffer`的set。其中buffer需要一个`InputStream`。location需要字符串或者`concat` 若事务开启,则会在commit和rollback时把使用过的InputStream关闭。 ##Scala特殊用法 这里不作过多叙述,如下代码一目了然: ```scala import entity._ query from entity where age > 18 & id <> 1 param (orderBy(name.desc) top 1) list() query from entity where age > 18 & id <> 1 param (orderBy(name.desc) top 1) select id ~(name, "a") query from entity stream() filter age > 18 & id <> 1 sorted name.desc limit 5 list() query from entity stream() filter age > 18 & id <> 1 sorted name.desc limit 5 map id ~(name, "a") list() ``` ##DataSource `DataSource`用于将`DataAccess`中的逻辑分离,以减少编码失误,也有利于单元测试的进行。 `DataSource`模块分为以下几个主要类/接口 * DataSource 入口,规定模块中的其它实现 * ConditionParser 条件解释器 * ExpressionParser 表达式解释器 * AndOrParser 与,或解释器 * AroundParser 全局解释器 * QueryParameterParser 查询参数解释器 * EntityDataParser 实体字段解释器(也包括子查询的解释) * UpdateEntryParser 更新目标解释器 详情参考文档以及JPQLDataSource实现