资讯专栏INFORMATION COLUMN

[Java][数据库工具][坑] Juice README

CoXie / 1749人阅读

摘要:注意供应器只会在仓库工厂第一次创建工厂时调用,而参数处理器和结果解析器将在每次仓库方法被调用时调用。解析器接收一个语句表模型的类声明触发解析器的仓库方法声明。因此当您配置了一个结果解析器,语句的执行时机将推迟到这里。

Juice

这是我自己做的一个小项目,也可能会弃坑... 留作纪念吧。GitHub 地址

简介

Juice 是一个简易的、尚不完善的基于 JavaSQL数据库工具,它提供了对SQL语句最大程度的控制,和一点简单的扩展能力。

这些是开发时的一点笔记:
做个数据库帮助库雏形
做个数据库帮助库雏形2

使用效果
RepositoryFactory factory = RepositoryFactory.configure(ConnectionConfiguration.builder()
                .driverClass("com.mysql.jdbc.Driver")
                .connectionURL("jdbc:mysql://localhost:3306/hsc")
                .username("gdpi")
                .password("gdpi")
                .build());

StudentRepository repository = factory.get(StudentRepository.class);

List studentList = repository.findAll();
// LinkedList size: 56

Student student = repository.getNameById("20152203300");
// {name: "krun", id: null, college: null, ...}

int count = repository.updateGenderById("20152203300", "男");
// 1

Student student2 = repository.findById("20152203300");
// {name: "krun", id: "20152203300", gender: "男",  major: "软件技术", ...}
功能与使用

使用 Juice 只需要简单的几步:

注: 本示例使用 lombokmysql-connector(5.1.44)

数据库连接配置: ConnectionConfiguration

当前版本的 Juice 只需要以下几个参数用以连接数据库:

driverClass:这个参数用于向驱动管理器注册一个数据库连接驱动。(本示例将使用 com.mysql.jdbc.Driver)

connectionURL: 这个参数用于向驱动管理器获取一个数据库连接,常用的如:jdbc:mysql://localhost:3306/juice,您可以附带任何连接语句中允许附加的参数,如字符编码设置等等。

username: 这个参数是获取数据库连接时所需要的数据库账户名

password: 这个参数是获取数据库连接时所需要的数据库密码

不建议直接在 connectionURL中配置连接所需的数据库账户及密码。

在未来的版本中,Juice会尝试加入对 *.properties文件的支持,如此一来,您可以直接在 *.properties文件中设置连接的详细参数。对MySQL适用的 properties选项请参见这里。

在 Java SE 环境中,您可以通过 ConnectionConfiguration.builder()构造器来构造一个配置:

ConnecetionConfiguration conf = ConnectionConfiguration.builder()
                                      .driverClass("com.mysql.jdbc.Driver")
                                      .connectionURL("jdbc:mysql://localhost:3306/juice")
                                      .username("gdpi")
                                      .password("gdpi")
                                      .build();

如果是类似 Spring 这样可以配置 Bean实例的环境中,您可以使用类似如下的方式来以Bean的方式创建一个配置:

 
     
     
     
     
仓库工厂: RepositoryFactory

仓库工厂是创建、管理仓库的地方。Juice 允许在一个 Java Application 中存在多个仓库工厂的实例,但由于每个仓库工厂都会持有一个 数据库连接供应器(ConnectionProvider) ,因此建议使用默认全局工厂。

每个工厂都由一个自己的名字,默认全局工厂的名字为: global, 这并不是一个常量值,为了避免某些情况下发生冲突,Juice 允许你在创建前修改 RepositoryFactory.FACTORY_GLOBAL 的值来更改默认全局工厂的名字。请注意,如果您在创建全局工厂后修改了该值,那么再次使用 不指定名称的工厂获取方法(RepositoryFactory.get())将导致重新创建一个以新值命名的全局工厂。

在使用仓库工厂前,需要传入一个 ConnectionConfiguration实例,使仓库工厂得以初始化内部的数据库连接供应器。

在 Java SE 环境中,您可以通过下面的方式来配置仓库工厂:

//这里的 conf 即为前一节所创建的数据库连接配置

// 配置全局仓库工厂
RepositoryFactory globalFactory = RepositoryFactory.configure(conf);

// 配置指定名称的仓库工厂
RepositoryFactory fooFactory = RepositoryFactory.configure("foo", conf);

// 请注意,使用第二种方式配置工厂时,使用默认全局工厂名称将抛出错误,因为这会破坏 API 所划分的全局、特定工厂的界限
RepositoryFactory wrongFactory = RepositoyFactory.configure(RepositoryFactory.FACTORY_GLOBAL, conf);
// > RuntimeException

如果是类似 Spring 这样可以配置 Bean实例的环境中,您可以使用类似如下的方式来以Bean的方式创建仓库工厂:

 
     


 
      
     

在配置仓库工厂后,您可以通过 RepositoryFactory.get()RepositoyFactory.get(name)来获取全局或给定名称的仓库工厂。

表模型

Juice 可以将您给定的一个 Java 类视为一个表模型,就像下面这样:

@Data
@Entity("student")
public class Student {

   private String id;

   @Column("class")
   private String clazz;

   private int code;
   private String college;
   private String gender;
   private int grade;
   private String major;
   private String name;

}

@Data 注解来自 lombok

@Entity 注解是一个可选项,它只有一个必填属性: value。当配置该注解时,Juice将使用该值作为表名;如果您指定了这个类是个表模型,Juice 却找不到该注解时,将使用类名的全小写形式作为表名。

@Column注解同样是一个可选项,它只有一个必填属性: value。当配置该注解时,Juice将使用该值作为数据库中此表的字段名,否则使用 Java 类字段名作为数据库中此表的字段名。

仓库: Repository

Repository 是一个注解,它实际上只是一个用于表明某个接口是一个仓库的标记。就像下面这样:

public interface StudentRepository extends Repository {

    @Query (value = "SELECT * FROM %s")
    List findAll();

    @Query (value = "SELECT * FROM %s WHERE id = ?")
    Student findById(String id);

     @Query (value = "UPDATE %s SET gender = ? WHERE id = ?",
            processor = StudentChain.class,
            processMethod = "replaceParameterLocation")
     Integer updateGenderById(String id, String gender);

     @Query ("SELECT name FROM %s WHERE id = ?")
     Student getNameById(String id);

}

Repository需要填入两个泛型信息,第一个是该仓库所操作的表模型,第二个是该表模型的主键类型。

注: 事实上到目前为止,Juice 并不区分主键和其他字段,只是为了以后完善留下空间。

@Query 注解

由于到目前为止,Juice 短期内不会实现 解析方法名并映射为一个SQL操作 这个 feature, 因此需要 @Query 注解来标记一个方法,并以此提供一些信息,Juice 提供的扩展能力也在这里体现:

@Query注解具有以下七个属性:

String value: 这个属性指定了方法所映射的 SQL操作,其中有着一些约定:%s占位符用于 Juice 填充表名,而 ? 占位符是 PreparedStatement 所使用的参数占位符。由于 Juice 提供简单的默认实现,这些默认实现使用的就是 PreparedStatement,因此如果您使用了不一样的Statement实现,您可以使用任何与之配合的占位符。注意:如果您选择了使用 %*系列作为占位符,那么请记得第一个 %s将会被 Juice 用来填充表名。

Class provider: 这个属性指定了语句供应器所处的类,您可以指定任何实现了RepositoryStatementProvider接口的类,默认值为 Juiec 提供的DefaultPreparedStatementProvider,详细信息请参见下文。

String provideMethod: 这个属性指定了注解所在方法所使用的语句供应器,当provider 属性使用默认值时,此属性无效;默认值为注解所在方法的名字或provide

Class processor: 这个属性指定了参数处理器所处的类,您可以指定任何实现了 RepositoryParameterProcessor接口的类,默认值为 Juiec 提供的默认参数处理器 DefaultParameterProcessor,详细信息请参见下文。

String processMethod: 这个属性指定了注解所在方法所使用的参数处理器,当processor属性使用默认值时,此属性无效;默认值为注解所在方法的名字或 process

Class resolver: 这个属性指定了结果解析器所处的类,您可以指定任何实现了 RepositoryResultResolver接口的类,默认值为 Juiec 提供的 DefaultResultResolver,详细信息请参见下文。

String resolveMethod: 这个属性指定了注解所在方法所使用的结果解析器,当resolver属性使用默认值时,此属性无效;默认值为注解所在方法的名字或 resolve

注意

您所指定的 provideMethodprocessMethodresolveMethod都必须是静态方法,这并无太多考量,只是为了减轻 Juice 的对象管理成本。

语句供应器 RepositoryStatementProvider

一个语句供应器的方法签名应该如下:

public static Statement provideMethodName(Connection connection, String sql)

供应器所在的类是 @Query.provider 的值,方法名是 @Query.provideMethod 的值。

供应器接收一个 java.sql.connection@Query.value值,并返回一个 java.sql.statement

这里的 sql 已经填充了表名

这里的Connection可以不关闭,它会由仓库工厂进行复用。

注意:供应器只会在仓库工厂第一次创建工厂时调用,而参数处理器和结果解析器将在每次仓库方法被调用时调用。

如果您希望使用项目所特定的、实现了装饰器模式的、特殊的Statement实例,可以为方法定义一个、或创建一个全局的语句供应器,并为所有方法指定。

也许后期会在 factory 中加入替换默认语句供应器、参数处理器、结果解析器的接口。

默认的语句供应器 DefaultPreparedStatementProvider.provide将根据给定 sql创建一个 com.mysql.jdbc.PreparedStatement实例。

参数处理器 RepositoryParameterProcessor

一个参数处理器的方法签名应该类似下面这样(这里对应的是 StudentRepository.findById):

public static Statement findById (Statement statement, String id)

处理器所在的类是 @Query.processor的值,方法名是 @Query.processMethod 的值。

处理器接收一个java.sql.statement和具体的参数列表,并返回一个java.sql.statement

如果您希望在每次方法调用时都有个地方可以记录日志、进行参数检查,可以为其配置一个参数处理器。

在当前版本的 Juice 中,如果您希望处理类似下面这种情况:

public StudentRepository extends Repository {
  
  @Query("INSERT INTO %s (%s) VALUES (%s)")
  Integer insert(Student student);
  
}

您需要为其配置一个语句供应器:

public static Statement insert(Connection connection, String sql) {
    return connection.prepareStatement(
      String.format(sql,
        StringUtils.convertObjectFields2StringList(Student.class)));
}

和一个参数处理器:

public static Statement insert(Statement statement, Student student) {
    PreparedStatement ps = (PreparedStatement) statement;
      for (Field field : student.getClass().getDeclaringFields()) {
      field.setAccessable(true);
      ps.setObject(index, field.get(student));
    }
}

以上均为伪代码

Juice 所提供的默认参数处理器 DefaultParameterProcessor,只是简单得把参数按顺序填充入SQL语句中并返回。因此,类似下面这种情况可能会发生错误:

public StudentRepository extends Repository {
    
  @Query("UPDATE %s SET gender = ? WHERE id = ?")
  Integer setGenderById(String id, String gender);
  
}

setGenderById的参数列表中,id在前,gender·在后,这会使得DefaultParameter.process输出:

UPDATE student SET gender = {id} WHERE id = {gender}

显然这是错误的。如果要避免这种情况,可以直接把方法的参数列表按 SQL语句中的参数顺序排放;也可以为其指定一个参数处理器用以调整参数填充顺序。

结果解析器 RepositoryResultResolver

一个结果解析器的方法签名应该类似下面这样:

public static Object resolve(Statement statement, Class entityClass, Method method)

解析器所在的类是 @Query.resolver 的值,方法名是@Query.resolveMethod的值。

解析器接收一个java.sql.statement语句、Class表模型的类声明、Method触发解析器的仓库方法声明。

这里的 statement 尚未执行,因为java.sql.statement.execute系列接口需要一些额外参数,这导致 Juice无法确保一致的行为。因此当您配置了一个结果解析器,语句的执行时机将推迟到这里。

Juice 所提供的默认解析器 DefaultResultResolver有着很多限制:

只支持解析仓库所声明的表模型类型和其List形式

对于 INSERT/UPDATE/DELETE操作,只会返回Integer数值用以表示该SQL操作影响的行数

不支持表模型字段含有其他非SQL types类型的递归、嵌套解析

因此,如果您希望能解析复杂的结果,例如将前一节中的 insert操作返回插入后的结果并映射为一个Student:

public StudentRepository extends Repository {
    
  @Query("UPDATE %s SET gender = ? WHERE id = ?")
  Student setGenderById(String id, String gender);
  
}

那么还需要配置一个结果解析器:

public static Student insert(Statement statement, Class entityClass, Method method) {
    // 解析逻辑...
}
结束

那么, Juice 的介绍、使用帮助就到此结束了,感谢您的观看 : )

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70572.html

相关文章

  • 如何写好技术简历 —— 实例、模板及工具

    摘要:在线简历生成工具,可以导出。技巧目前写简历的方式有两种普遍被认可,一种是一种是。培养新人和带团队其他项目项目该项目是,使用技术,完成功能。阅读原文点击查看简历模板。 工欲善其事必先利其器,这是自古以来的道理,所以如果想找到一份好的工作,一定要先整理一份好的简历。 模板 写简历首先要有一个好的模板,我们做技术的不同于 UX,UED,我们不需要那么花哨,但是也需要整洁干净。好的模板能让你的...

    sunnyxd 评论0 收藏0
  • ES6入门笔记(一)

    摘要:用声明的常量无法在后面的代码中改值。表达式里还有一个很方便的就是表达式,举个例子运行结果为后声明的里以数组的形式存放了函数的剩余参数,是不是很方便。 ES6入门笔记(一) 安装babel 由于浏览器对ES6的支持还不是很好,编写ES6代码前我们要安装一个babel工具将ES6代码编译成ES5代码,用如下命令安装babel: npm install -g babel-core ...

    warkiz 评论0 收藏0
  • Java 8之stream实际应用

    摘要:前言在前面的之介绍和使用和之进阶中讲了的使用方式和一些常用的方法,这篇文章就来演示一下的实际应用。实际应用先创建一个订单类和商品类,每个订单都有年份商品数量和商品对象属性,而商品类里面则包含了名字和价格属性。 前言: 在前面的 Java 8之stream介绍和使用 和 Java 8之stream进阶 中讲了stream的使用方式和一些常用的方法,这篇文章就来演示一下stream的实际应...

    jonh_felix 评论0 收藏0
  • 设计模式(10)状态模式(讲解+应用)

    摘要:状态模式对于对象内部的状态,允许其在不同的状态下,拥有不同的行为,对状态单独封装成类。通过什么来举例子呢设计到多状态,不同状态下各自具有不同行为的东西,而且理解起来相对容易的。下篇更新适配器模式 目录 状态模式 为什么使用状态模式? 应用实例 状态模式 状态模式,顾名思义,肯定是和状态有关,进一步思考,我们在讨论的是设计模式,设计模式中的重要原则对变化的进行封装,顺着这个思路去想,...

    lakeside 评论0 收藏0

发表评论

0条评论

CoXie

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<