这是我本身作的一个小项目,也可能会弃坑... 留做记念吧。GitHub 地址java
Juice 是一个简易的、尚不完善的基于 Java 的SQL数据库
工具,它提供了对SQL语句
最大程度的控制,和一点简单的扩展能力。mysql
这些是开发时的一点笔记:
作个数据库帮助库雏形
作个数据库帮助库雏形2sql
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<Student> studentList = repository.findAll(); // LinkedList<Student> 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 只须要简单的几步:数据库
注: 本示例使用 lombok
和 mysql-connector(5.1.44)
segmentfault
ConnectionConfiguration
当前版本的 Juice 只须要如下几个参数用以链接数据库:ide
driverClass
:这个参数用于向驱动管理器注册一个数据库链接驱动。(本示例将使用 com.mysql.jdbc.Driver
)connectionURL
: 这个参数用于向驱动管理器获取一个数据库链接,经常使用的如:jdbc:mysql://localhost:3306/juice
,您能够附带任何链接语句中容许附加的参数,如字符编码设置等等。username
: 这个参数是获取数据库链接时所须要的数据库帐户名password
: 这个参数是获取数据库链接时所须要的数据库密码不建议直接在
connectionURL
中配置链接所需的数据库帐户及密码。工具
在将来的版本中,Juice
会尝试加入对 *.properties
文件的支持,如此一来,您能够直接在 *.properties
文件中设置链接的详细参数。对MySQL适用的 properties
选项请参见这里。ui
在 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
的方式建立一个配置:日志
<bean id="connectionConfiguration" class="com.krun.juice.connection.configuration.ConnectionConfiguration"> <constructor-arg name="driverClass" value="com.mysql.jdbc.Driver" /> <constructor-arg name="connectionURL" value="jdbc:mysql://localhost:3306/juice" /> <constructor-arg name="username" value="gdpi" /> <constructor-arg name="password" value="gdpi" /> </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
的方式建立仓库工厂:
<bean id="globalFactory" class="com.krun.juice.repository.factory.RepositoryFactory"> <constructor-arg ref="connectionConfiguration" /> </bean> <bean id="fooFactory" class="com.krun.juice.repository.factory.RepositoryFactory"> <constructor-arg name="name" value="foo" /> <constructor-arg name="connectionConfiguration" ref="connectionConfiguration" /> </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<Student, String> { @Query (value = "SELECT * FROM %s") List<Student> 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<? extends RepositoryStatementProvider> provider
: 这个属性指定了语句供应器所处的类,您能够指定任何实现了RepositoryStatementProvider
接口的类,默认值为 Juiec 提供的DefaultPreparedStatementProvider
,详细信息请参见下文。String provideMethod
: 这个属性指定了注解所在方法所使用的语句供应器,当provider
属性使用默认值时,此属性无效;默认值为注解所在方法的名字或provide
。Class<? extends RepositoryParameterProcessor> processor
: 这个属性指定了参数处理器所处的类,您能够指定任何实现了 RepositoryParameterProcessor
接口的类,默认值为 Juiec 提供的默认参数处理器 DefaultParameterProcessor
,详细信息请参见下文。String processMethod
: 这个属性指定了注解所在方法所使用的参数处理器,当processor
属性使用默认值时,此属性无效;默认值为注解所在方法的名字或 process
。Class<? extends RepositoryResultResolver> resolver
: 这个属性指定告终果解析器所处的类,您能够指定任何实现了 RepositoryResultResolver
接口的类,默认值为 Juiec 提供的 DefaultResultResolver
,详细信息请参见下文。String resolveMethod
: 这个属性指定了注解所在方法所使用的结果解析器,当resolver
属性使用默认值时,此属性无效;默认值为注解所在方法的名字或 resolve
。注意:
您所指定的 provideMethod
、processMethod
、resolveMethod
都必须是静态方法,这并没有太多考量,只是为了减轻 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<Student, String> { @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<Student, String> { @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<Student, String> { @Query("UPDATE %s SET gender = ? WHERE id = ?") Student setGenderById(String id, String gender); }
那么还须要配置一个结果解析器:
public static Student insert(Statement statement, Class<?> entityClass, Method method) { // 解析逻辑... }
那么, Juice 的介绍、使用帮助就到此结束了,感谢您的观看 : )