[Java][數據庫工具][坑] Juice README

Juice

這是我本身作的一個小項目,也可能會棄坑... 留做記念吧。GitHub 地址java

簡介

Juice 是一個簡易的、尚不完善的基於 JavaSQL數據庫工具,它提供了對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 只須要簡單的幾步:數據庫

注: 本示例使用 lombokmysql-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

注意

您所指定的 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<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 的介紹、使用幫助就到此結束了,感謝您的觀看 : )

相關文章
相關標籤/搜索