這是我本身作的一個小項目,也可能會棄坑... 留做記念吧。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 的介紹、使用幫助就到此結束了,感謝您的觀看 : )