Spring JDBCベースの軽量級ORM-sbormの紹介

30150 ワード

【事業所住所】:https://github.com/franticwind/sborm【ダウンロード先】:https://github.com/franticwind/sborm/releases【注】:初めて多くの人がツッコミを入れ、改版を経てAPIが最適化された.
一、sborm紹介
1、現在mysqlのサポートしか考えていない.
2、spring jdbcに基づく上層パッケージ、下層jdbc操作はJdbcTemplateに基づいており、spring jdbcを使用する人には少し価値があり、比較的簡潔なパッケージは多くの重複労働を節約することができ、具体的にどのくらい節約するかはexampleを見ることができる.
3、簡単なORM(spring rowmapper、insertを直接使用して自分で実現する)を実現し、オブジェクトに基づいてcrudと比較的複雑な(hibernateより強い感じ)sql操作を行うことができる.
4、オブジェクトに基づいてクエリーのフィールドを指定し、ほとんどの場合、テーブル構造を忘れて業務開発を行うことができる.
5、簡単なデータベースルートをサポートし、読み書き分離(半自動、writerかreaderかを指定する必要があり、デフォルトのルールreaderはランダムな方式を採用し、もちろん手動で指定することもできる).
6、简単な分表をサポートして、主に一定の规则の分表に対して、例えばパーセンテージ表、千分表、自分で分表の接尾辞を指定することができます;
7、簡単な単一テーブルクエリー(例えば、すべての条件がandまたはor構造である)、基本的に0 sqlコードの作成(HibernateTemplate selectByExample、findByCriteria、findなどの方法)を実現する.
8、簡単な単表並べ替えサポート、複数の並べ替え条件の組み合わせをサポートする.
9、複雑なsqlクエリーに対して、spring jdbcの一般的な使い方に似たjdbctemplateインスタンスを取得して操作することを提供する.
10、Entityコード生成インタフェースを提供し、Entityは簡単なpojoではなく(できるだけこのようなものを修正しない)、フィールド定数クラスを導入し、クエリーの時に選択フィールドを指定しやすくし、クエリー条件のパッケージをよりよく実現する.
二、どうしてsbormと書きますか.
1、hibernate:肥大化しすぎて、使用が柔軟ではなく、最適化が難しい(実は主に少ないため)、HQLはスラグだと感じ、mysqlがほぼ天下を統一している背景の下で、データベースレベルを超えた互換性が苦労している.Hibernateの対象化関連処理は確かに強いが、ピットが多すぎて、プロジェクトで広範囲に使用する勇気がある人がどれだけいるか分からない.屠龍刀は誰もが持っているものではないだろう.
2、mybatis:軽量級、xmlベースのモードはパッケージ化に不利で、コード量は小さくなく、xmlベースのメンテナンスも面倒(個人的な観点では、今の注釈モードも悪くないようだが)、mybatisはdbaロールが存在する年代に適していると感じ、コードから離れてsqlのチューニングを行うことができ、複雑なクエリーの組み立てもより優雅である(javaは基本的にif else...).しかし、クエリー業務は簡単だがデータベースクラスタ環境のシーンには少し息苦しい(実はmybatisにもあまり使われていないので、でたらめなコメント^^).
3、spring jdbc:小さくて、柔軟で、十分に優秀で、個人は比較的に使うのが好きですが、コード量が大きくて、原生のインタフェースの繰り返し労働量が大きくて、例えばinsert、mapperなど;
sbormはspring jdbcのいくつかの不便な点に対して、いくつかのパッケージをして、更に日常の開発の仕事を簡略化して、spring jdbcのRowMapperに基づいて自動的にオブジェクトのマッピングを実現して、無理に計算する上でORMと言って、ただ大部分の機能はすでにspring jdbcによって実現しました.
普段はhibernateやmybatisを使うのがあまり好きではありません.主にspring jdbcを使います.これを書く出発点は主にspring jdbcを使うのが面倒で、重複性のあるコードが多いことです.論理を変更し、類似性のあるSQL文を書きすぎないようにし、DAOインタフェースの量を減らします.
三、いくつかのハイライト
1、Entityのデザイン:多くの人が見て、これはPOJOではなく、純粋なJava Beanではなく、変わっているように見えるかもしれません.しかし、多くの人が開発中(特にsqlを書くとき)、表構造の設計をよく見に行きますか?テーブルのフィールドを変更したため、どのsqlがこのフィールドを使用しているかを検索するには何回ありますか?コードにフィールド名が直接入力されてクエリーパラメータとして違和感を感じるのは何回見ましたか?テーブル構造フィールドをjavaオブジェクトで記述すれば、これらの問題を解決できるので、POJOかどうかを気にする必要はありません.後でexampleを見るときに、このようなメリットを体得できるはずです.少なくとも便利だと思います.ほとんどのクエリーをテーブル構造設計から離れます.
2、簡単なデータベースのルート:ライブラリ構造があまり複雑でない場合(例えば簡単な読み書き分離、あるいは複数のライブラリ統合)、BaseDaoは自動的にルートを行うことができ(例えば読み書き分離、業務モードによって読み書きライブラリを指定)、デフォルトのルート規則でない場合、手動で設定したモードを通じて、データベースのルートを行うことができる.データベースルーティングは直接Entityによって指定され、すべてのルーティングはEntityによって識別されます.つまり、クエリーもEntityの周りに展開されます.spring jdbcのような使用を避けるために、さまざまなtemplateインスタンスがジャンプし、ハードコーディングが導入され、ビジネスを書くには、どのtemplateを使用するべきか、特に複数のデータベースが1つのtemplateインスタンスを共有する場合を見なければなりません.
3、QueryBuilder:単一テーブルクエリは基本的にゼロSql(クエリ条件が特に複雑でない限り)を実現することができ、更新、削除などの操作はQueryBuilderによって一括処理を行うことができ、プライマリ・キーに基づいて処理することに限らない.
4、分表操作のサポート:分表操作と従来の使用に違いはなく、分表ルールを指定するだけで、mybatisもパラメータを制定することで分表処理を実現できるようで、hibernateがこれに対してどのように処理しているのか分からない(hibernateはbeanと表が一対一にバインドされているようだ).
5、改造RowMapper:BeanPropertyRowMapperに基づいて、いくつかの修正を行い、改善構想は主に別名、関数、連合クエリーモードの下で、単一beanがいくつかのフィールドの問題を受信できないため、BaseEntityを通じてmapを設定し、これらのデータを保存し、改造したBaseEntityRowMapperのmapRow方法の中で特殊な処理を行い、詳細な方法は私のブログを見てください:『Spring JDBC RowMapperに関する少しの改善構想』
6、Entity組織クエリー条件に基づく:多くの友人はQueryBuilder組織クエリー条件に基づくコード構造が非常に複雑であるとフィードバックし、コード生成を利用して、各Entityに内部クラスEntity QueryBuildrを追加し、各フィールドに基づいて各種クエリー方法を編纂し、主にwhere、orderの2つのモードであり、group、havingなどの文法が必要であれば、やはりQueryBuilderに依存しなければならない.コード構造は次のとおりです(詳細はDemoクラス、および後述のExampleを参照してください).
public void selectColumn(String ... columns) {
    entity.getQueryBuilder().columns().select(columns);
}
public EntityQueryBuilder whereIdEQ (Object value) {
    entity.getQueryBuilder().where().add(QueryCondition.EQ(Columns.id, value));
    return this;
}
public EntityQueryBuilder whereIdNEQ (Object value) {
    entity.getQueryBuilder().where().add(QueryCondition.NEQ(Columns.id, value));
    return this;
}

四、使用説明
1、構成説明
主にいくつかの部分に分けられます.
a、datasource構成:データソース、接続プールなどを含み、普通のspring構成と一致する.
demo(接続プールの使用は個人の好みによる):


    
    
    
    
    
    
    
    
    


    
    
    
    


b、database router配置:类似jdbctemplate配置(内部会创建jdbctemplate对象),代替原来jdbctemplate配置,举例说明

原JdbcTemplate配置如下:



    
        
    


DatabaseRouter配置如下:



    
        
            key1,W,datasource
            key2,R,datasource
            key3,R,datasource
        
    
    


class: com.sborm.core.DatabaseRouter为数据库路由类,负责数据库信息映射,jdbctemplate实例创建,数据库路由(读写分离)等;
servers:服务器列表,通常会存在主从的架构,通常一台服务器需要配置一个datasource,对应需要配置一个database实例,key1,W,datasource,对应分别是database标识(单个database router内不能重复)、服务器属性(A:读写,W:写,R:读)、datasource引用;
dbName:数据库名称,需要指定,通过Entity实例的注解获取路由;

以上是所有必须配置,其实和DataSource、JdbcTemplate结合配置类似,只是封装了JdbcTemplate的实例化和自动选择。
**和JdbcTemplate配置区别:**JdbcTemplate淡化数据库的概念,围绕数据库服务器展开,多个数据库如果在同一台物理服务器上,都可以共用一个实例,但是一个数据库有多个节点需要配置多个template实例(基于不同的datasource),当然也可以保持和datasource 1:1的配置,模糊数据库的概念,在具体查询业务中指定数据库。
DatabaseRouter围绕数据库展开,可以配置1个或者多个节点,更多是从分布式数据库架构作为设计基础,datasource的配置量还是和物理服务器数量保持一致,但是每个数据库必须配置一个bean,bean可以引用多个节点的datasource,每个节点可以配置读、写属性,自动读写分离将根据该属性进行判断。

2、数据库路由选择规则

至少要配置一个,只能配置单个写库,可以配置多个读库,具体在DatabaseRouterFactory类中实现;
提供以下方法:

/**
 *          ,     key        ,   entity  key
 * default        
 * @param entity
 * @return
 */
public static JdbcTemplate getDefault(BaseEntity entity);

/**
 *           ,     key        ,   entity  key
 *         ,    
 * @param entity
 * @return
 */
public static JdbcTemplate getReader(BaseEntity entity);

/**
 *           ,     key        ,   entity  key
 *         ,    
 * @param entity
 * @return
 */
public static JdbcTemplate getWriter(BaseEntity entity);

3、Entity構造の生成説明
@Database("test")       //        test
@Table("demo")          //           demo
@Component          //         spring    ,                   ,     
public class Demo extends BaseEntity implements Serializable {

    public static final long serialVersionUID = 840187169979836738L;

    public static final class Columns {
        public static final String id = "id";
        public static final String name = "name";
        public static final String password = "password";
        public static final String createTime = "createTime";
        public static final String type = "type";
    }

    private Integer id;
    private String name;
    private String password;
    private Date createTime;
    private Integer type;

    // getter & setter ...
    //    ...

    //////////////////////////////
    //                  
    //////////////////////////////
    public EntityQueryBuilder query = new EntityQueryBuilder(this);

    public class EntityQueryBuilder {
        Demo entity = null;
        public EntityQueryBuilder(Demo obj) {
            entity = obj;
        }

        public void selectColumn(String ... columns) {
            entity.getQueryBuilder().columns().select(columns);
        }
        public EntityQueryBuilder whereIdEQ (Object value) {
            entity.getQueryBuilder().where().add(QueryCondition.EQ(Columns.id, value));
            return this;
        }
        //    ...
    }
}

@Databse、生成はデータベース名を指定し、Entityの情報に基づいてデータベースルーティングを行い、すべてのクエリーはEntityと読み書き分離規則に基づいて、関係jbdctemplateで問題を参照しない.@Table、マッピングされたテーブル名またはテーブル接頭辞(ルール行分割テーブルが必要)Columnsクラスは、テーブルのすべての列を解析し、Entityクエリーに基づいてクエリー条件を指定するのに便利で、フィールド情報のハードコーディングを避ける(大きな特色のようで、後にsqlクエリーがないのはこれに基づいていることが多い).
4、並べ替えの説明
ソートはOrderBuilderによって実現され、追加順序によって組み立てる.
QueryBuilder q = new QueryBuilder(demo);
q.order().add(Demo.Columns.createTime, OrderMode.DESC);         //       
q.order().addASC(Demo.Columns.createTime);              //       
q.order().addDESC(Demo.Columns.createTime);             //       

5、ページング処理説明
呼び出しインタフェース:public void select(QueryBuilder queryBuilder,PageResult)
6.エンティティークラススキャン(EntityScanner)について
主な機能はEntityのRowMapperを初期化し、table、databaseマッピング(使用時に初期化することもできるようで、よく考えていないので、先にそうします);コンフィギュレーションcontext:component-scanを構成する必要があります.各EntityにはComponent注記が追加されます.主にSpringでインスタンス化され、少し回ります.
7、コアクラスの説明
DatabaseRouter:データベースインスタンスの初期化、JdbcTemplateインスタンスの作成、読み書き分離規則処理などを担当する.DatabaseRouterFactory:databaserouterツールクラス;EntityContainer:エンティティコンテナで、主にRowMapperインスタンスを取得します(6点目の説明を参照して、EntityScannerによってRowMapperインスタンス化できます).GrammarBuilder:一連の構文アセンブリ抽象親;QueryCondition:クエリー・パラメータ・オブジェクト、パッケージ化された一般的なクエリー構文.=,<>,>=,QueryBuilder:クエリー条件エントリ・クラス、対外的に最もコアなクラス、オブジェクト・ベースの操作データベースのすべての処理は、ColumnBuilder、GroupBuilder、HavingBuilder、PageBuilder、WhereBuilderの組み合わせを含むクラスによって実現される.SQL:QueryBuilderからsqlへの翻訳処理を担当する.IBaseDao:daoベースインタフェースは、BaseDaoによって実現され、基本的なCRUD操作を提供し、比較的よく使われる方法、ページング方法などがあります.EntityGenerator:Entityコード生成ツールクラスは、プロファイル規則に基づいて生成できます(8点参照).
8、Entityコード生成ツールの使用説明
全部で3種類のコード生成モードを提供します.具体的には以下の通りです.
/**
 *           Bean,           
 * 
 * @param ip                   IP
 * @param port                   
 * @param database              
 * @param userName                
 * @param password               
 * @param table               
 * @param annotationTable       ,       ,              
 * @param className             , null         ,     ,
 *                                   package  ,   packageName  
 * @param packageName         
 * @param targetDir                 
 * @param encoding              
 * @throws Exception
 */
public static void generateTable(String ip, int port, String database,
        String userName, String password, String table, String annotationTable, String className,
        String packageName, String targetDir, String encoding);

/**
 *           Bean,         ,     ,                ,    classNameMapping    
 * 
 * @param ip                       IP
 * @param port                       
 * @param database                  
 * @param userName                    
 * @param password                   
 * @param includeTables              (             ,      )
 * @param excludeTables               (  ,       ,        )
 * @param classNameMapping          ,         ,     -    
 * @param packageName             
 * @param targetDir                   
 * @param encoding                  
 * @throws Exception
 */
public static void generateAllTable(String ip, int port, String database,
        String userName, String password, Map includeTables,
        String[] excludeTables, Map classNameMapping,
        String packageName, String targetDir, String encoding);

/**
 * 
 * (      )          Entity,                 ,             。
 * 
 *     ,1   2   3 ,    ,
 * 1 :  ,             ,        (     )
 * 2 (          ):      ,      (package     ,      ),    ,             
 * 3 (       ):      ,           ,      (package     ,      )
 * 
 * demo:
 *   ,  
 * table                        (          ,        Table)
 * table_test,TableTest         (   ,    table  table_test,     TableTest)
 * table_00,table_,Table           (            ,      table   table_,              Table)
 * ...
 * 
 * @param ip                   IP
 * @param port                   
 * @param database              
 * @param userName                
 * @param password               
 * @param configFilePath          
 * @param packageName         
 * @param targetDir                 
 * @param encoding            
 * @throws Exception
 */
public static void generateByFile(String ip, int port, String database, String userName, String password, 
        String configFilePath, String packageName, String targetDir, String encoding);

五、例(参照コードのexampleパッケージ)
package com.sborm.example.dao.impl;

import org.springframework.stereotype.Repository;

import com.sborm.core.dao.BaseDao;
import com.sborm.example.dao.ITestDao;

@Repository
public class TestDao extends BaseDao implements ITestDao {
}

実際にはこのクラスには何の方法もありません.
package com.sborm.example;

import java.util.Date;
import java.util.List;
import java.util.Random;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.sborm.core.PageResult;
import com.sborm.core.grammar.OrderMode;
import com.sborm.core.grammar.QueryBuilder;
import com.sborm.core.grammar.QueryCondition;
import com.sborm.core.grammar.QueryMode;
import com.sborm.example.bean.Demo;
import com.sborm.example.dao.ITestDao;

public class Example {
    static ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    static ITestDao dao = (ITestDao) ctx.getBean("testDao");

    /**
     *     
     */
    public static void testInsert() {
        try {
            Demo demo = new Demo();
            demo.setCreateTime(new Date());
            demo.setName("demo_" + new Random().nextInt(10000));
            demo.setType(new Random().nextInt(5));
            demo.setPassword("000000");
            dao.insert(demo);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *     
     */
    public static void testUpdate() {
        try {
            Demo demo = new Demo();
            demo.setName("newname");
            QueryBuilder q = new QueryBuilder(demo);
            q.where().add(QueryCondition.EQ(Demo.Columns.id, 1));   // =  
            int c = dao.update(q);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *     
     */
    public static void testDelete() {
        try {
            Demo demo = new Demo();
            QueryBuilder q = new QueryBuilder(demo);
            q.where().add(QueryCondition.EQ(Demo.Columns.id, 1));   // =  
            int c = dao.delete(q);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *     
     */
    public static void testSelect() {
        try {
            Demo demo = new Demo();
            QueryBuilder q = new QueryBuilder(demo);
            q.columns().select(Demo.Columns.id, Demo.Columns.name + " as name1");   //          ,     
            q.where()
                    .add(QueryCondition.EQ(Demo.Columns.name, "newname"))   // =  
                    .add(QueryCondition.BETWEEN(Demo.Columns.createTime, "2014-07-10 11", "2014-07-19 12")); // between  
            q.order().add(Demo.Columns.createTime, OrderMode.DESC);     //     
            List> list = dao.select(q);
            System.out.println(((Demo)list.get(0)).getAliasFields("name1"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *   bean querybuilder     
     */
    public static void testSelect2() {
        try {
            Demo demo = new Demo();
            demo.query.selectColumn(Demo.Columns.id, Demo.Columns.name);    //          ,           
            demo.query.whereNameEQ("newname");
            demo.query.whereCreateTimeBETWEEN("2014-07-10 11", "2014-07-19 12");
            demo.query.orderByCreateTimeDESC();
            //   1:  entity  ,  AND    where  
            List list = dao.selectByExample(demo);
            System.out.println(list.size() + "  --  " + ((Demo)list.get(0)).getName());
            //   2:  entity  ,       where  
            list = dao.selectByExample(demo, QueryMode.OR);
            System.out.println(list.size() + "  --  " + ((Demo)list.get(0)).getName());
            //   3:    querybuilder  ,    where      ,   and
            list = dao.select(demo.getQueryBuilder().setQueryMode(QueryMode.AND));
            System.out.println(list.size() + "  --  " + ((Demo)list.get(0)).getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *     ,    
     */
    public static void testSelectPage() {
        try {
            Demo demo = new Demo();
            QueryBuilder q = new QueryBuilder(demo);
            q.columns().select(Demo.Columns.id, Demo.Columns.name); //          ,     
            //      where      ,or  and
            q.where(QueryMode.OR)
                    .add(QueryCondition.EQ(Demo.Columns.name, "test"))  // =  
                    .add(QueryCondition.BETWEEN(Demo.Columns.createTime, "2014-07-15 11", "2014-07-19 12")); // between  
            q.order().add(Demo.Columns.createTime, OrderMode.DESC);     //     

            PageResult pr = dao.select(q, 1, 1);
            System.out.println(pr.getResultCount() + " - " + pr.getPageCount());
        } catch (Exception e) {

        }
    }

    /**
     *   bean querybuilder       ,  testSelect2
     */
    public static void testSelectPage2() {
        try {
            Demo demo = new Demo();
            demo.query.whereNameEQ("test");
            demo.query.whereCreateTimeBETWEEN("2014-07-15 11", "2014-07-19 12");
            demo.query.orderByCreateTimeDESC();
            PageResult pr = dao.selectByExample(demo, QueryMode.OR, 1, 1);
            System.out.println(pr.getResultCount() + " - " + pr.getPageCount());
        } catch (Exception e) {

        }
    }

    public static void main(String[] args) {
        //testInsert();
        //testUpdate();
        //testDelete();
        testSelect2();
        testSelectPage();
        testSelectPage2();
        System.out.println("finish");
    }
}

【事業所住所】:https://github.com/franticwind/sborm【ダウンロード先】:https://github.com/franticwind/sborm/releases