After a year of development, the first official version of the Java persistence layer tool jSqlBox was released

After a year of development, the Java persistence layer tool jSqlBox finally launched its first official version, version 1.0.0. For details, please refer to the project address: https://gitee.com/drinkjava2/jSqlBox. The following is an introduction to the project content:

jSqlBox is a Java persistence layer tool, designed to replace the current persistence layer tools such as Hibernate/MyBatis/JdbcTemplate/DbUtils . The main feature of jSqlBox:

    Modular design. jSqlBox divides functions such as Jdbc tools, transactions, dialects, and database script generation into sub-modules, which are implemented by different sub-projects. Each sub-project can exist without jSqlBox, and can even be used by other ORM tools to avoid reinventing the wheel. At the same time, each sub-module of jSqlBox (except TinyNet) has borrowed a lot of other mature projects during development. For example, the dialect module (jDialects) extracts 70 dialects of Hibernate, and the transaction module (jTranscations) provides a simplified version of declarative transactions. At the same time, it is compatible with Spring transactions. The underlying database access tool (jDbPro) is based on Apache Commons DbUtils (hereinafter referred to as DbUtils). The introduction of chain style and templates is inspired by jFinal and BeetlSql respectively.
    Compatible with DbUtils, projects developed based on DbUtils can be seamlessly upgraded to jSqlBox, and enjoy the advanced functions provided by jSqlBox such as cross-database development, paging, templates, JPA annotation support, database script generation, ActiveRecord style, etc.
    Supports many ways to write SQL. Thanks to the modular architecture, jSqlBox has everything from the lowest level JDBC to the latest NoSQL queries, and is currently the persistence layer tool that supports the most SQL writing methods.
    Smooth learning curve and good maintainability. Thanks to the modular architecture, the ontology part of the jSqlBox project consists of only 13 Java classes. The work of the persistence layer is dispersed into various sub-modules. The benefit of modular design is modular learning. As long as you understand the functions of each module, you can quickly master the use of jSqlBox. The most extreme situation is that DbUtils users can directly use jSqlBox without learning. A
    number of technological innovations, such as Inline style, dynamic configuration, improved chain style, NoSQL automatic leapfrog query and tree structure query, etc.

The development purpose of jSqlBox is to try to solve some problems of other persistence layers, mainly including:
    problems with the architecture and repeated invention of the wheel. Other persistence layers usually try to provide a basket of solutions, but the non-module development approach makes the code non-reusable. For example, tools such as Hibernate, MyBatis, jFinal, NutzDao, BeetlSql, JdbcTemplate are faced with the problem of how to deal with cross-database development. They either develop a set of dialect tools from beginning to end, or simply do not support database dialects. I did not borrow the results of other tools, nor did I consider sharing my own results with other tools.
    Getting too paranoid about one technology and trying to fix everything with one hammer. For example, Hibernate and JPA object design is over-designed, bloated and complex. MyBatis's XML configuration is cumbersome and does not provide CRUD methods (there are plug-ins to improve, but it is not as good as native support). JdbcTemplate and DbUtils focus on SQL, and the development efficiency is low. BeetlSql revolves completely around templates. jFinal and Nutz have a soft spot for bundled sales, instead of separating the persistence layer, they bought a carrot cake.
    Dynamic configuration is not supported. From database tables to Java objects, called ORM, this requires configuration. However, these common persistence layer tools do not support dynamic configuration, which is a major flaw in situations where dynamic configuration is required to be generated or modified. For example, entity beans with JPA annotations or XML configuration are difficult to change the foreign key association relationship and database table mapping relationship configuration at runtime.

Disadvantages of jSqlBox

It is too new, it was born in less than a month (although it has been developed on Github and Code Cloud for more than a year), there are few users, design defects and bugs in the program have not been exposed, and the documentation It is not perfect, and it is not recommended to use it in important projects at present.
How to introduce jSqlBox into the project?

jSqlBox requires Java6 or above, add the following lines in the project pom.xml:

<dependency>
    <groupId>com.github.drinkjava2</groupId>
    <artifactId>jsqlbox</artifactId>
    <version>1.0 .0</version>
</dependency>

Maven will automatically download four packages: jsqlbox-1.0.0.jar, jdbpro-1.7.0.1.jar, jdialects-1.0.6.jar, commons-dbutils-1.7.jar. The following figure shows the dependencies between the various packages and the size of the packages and the number of source files.

If you need to configure transactions, you need to add a dependency on jtransactions-1.0.0.jar. For details, see the jTransactions project.
Quick Start to jSqlBox
The first jSqlBox example: data source setting, context generation, creating database tables, inserting and reading content

public class HelloWorld {
  @Id
  private String name;
  public String getName() {return name;}
  public void setName(String name) {this.name = name;}

  @Test
  public void doText() {
    HikariDataSource ds = new HikariDataSource();//Connection pool setting
    ds.setJdbcUrl ("jdbc:h2:mem:DBName;MODE=MYSQL;DB_CLOSE_DELAY=-1;TRACE_LEVEL_SYSTEM_OUT=0");
    ds.setDriverClassName("org.h2.Driver");//Using H2 in-memory database for unit testing
    ds. setUsername("sa");
    ds.setPassword("");

    SqlBoxContext ctx = new SqlBoxContext(ds); //jSqlBox context generation ctx.setAllowShowSQL
    (true);//Open jSqlBox log output
    String[] ddls = ctx. getDialect().toCreateDDL(HelloWorld.class);//Database DDL script generation
    for (String ddl : ddls)
      ctx.nExecute(ddl);//New database table

    HelloWorld hello = new HelloWorld();
    hello.setName("Demo");
    ctx.insert(hello);//Insert a row of records
    Assert.assertEquals("Demo", ctx.nQueryForObject("select name from helloworld"));
    ds.close();//Close the connection pool
  }
}

Run the unit test, no error is reported, indicating that a row of data has been successfully inserted into the database. The above example leverages the capabilities of jDialects to create a database script and run it in jSqlBox to create the database tables. jDialects supports 15 major JPA annotations and 9 primary key generation methods, see the jDialects project for details. In the actual development environment, IOC tools such as Spring or jBeanBox are often used to configure the connection pool singleton and the SqlBoxContext singleton, for example:

  SqlBoxContext ctx = BeanBox.getBean(CtxBox.class);//The SqlBoxContext singleton is obtained by the IOC tool

Use the IOC tool to configure the connection pool for various common databases. For details, see the DataSourceConfig.java example of the unit test. Because I am the author of jBeanBox, I use jBeanBox to demonstrate it. It is the same if I use Spring to configure it.
Paging


jSqlBox uses jDialects to perform cross-database (physical) paging. Where SQL appears, the following writing methods can implement paging queries:

In the above example, User inherits from the ActiveRecord class and has methods such as box(), which can dynamically access and change the TableModel configuration corresponding to the User class at runtime (see the jDialects project). affairs
















The jSqlBox project itself does not provide transaction services, but uses the jTransactions project for transaction management, which is an independent transaction service tool that includes two transaction implementations of TinyTx and SpringTx. If it is an old project ported from DbUtils, the original transaction operation mode can also be maintained, because jSqlBox is compatible with DbUtils.
The highlight - 10 SQL writing methods supported by jSqlBox:

SQL writing method 1 - DbUtils wraps JDBC, is responsible for obtaining and closing the connection manually, using the prepareConnection() method to obtain the connection, and using the close(conn) method to close the connection.
Because SqlBoxContext is a subclass of DbUtils' QueryRunner class, all methods of DbUtils can be used directly.

     Connection conn = null;
    try {
      conn = ctx.prepareConnection();
      ctx.execute(conn, "insert into users (name,address) values(?,?)", "Sam", "Canada");
      ctx.execute (conn, "update users set name=?, address=?", "Tom", "China");
      Assert.assertEquals(1L,
          ctx.queryForObject(conn, "
      ctx.execute(conn, "delete from users where name=? or address=?", "Tom", "China");
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        ctx.close (conn);
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }

SQL writing 2 - DbUtils's second JDBC wrapper, no need to care about the acquisition and closing of the connection, use this method to note that the generation of SqlBoxContext must provide A dataSource constructor parameter.

    try {
      ctx.execute("insert into users (name,address) values(?,?)", "Sam", "Canada");
      ctx.execute("update users set name=?, address=?", " Tom", "China");
      Assert.assertEquals(1L,
          ctx.queryForObject("select count(*) from users where name=? and address=?", "Tom", "China"));
      ctx.execute("delete from users where name=? or address=?", "Tom", "China");
    } catch (SQLException e) {
      e.printStackTrace();
    }

SQL writing 3 - nXxxx() method, a new method added from the jDbPro project, no need to catch SqlException. SqlException is converted into a runtime exception and thrown, which is usually used in conjunction with AOP tools that support declarative transactions such as Spring (the same below).

    ctx.nExecute("insert into users (name,address) values(?,?)", "Sam", "Canada");
    ctx.nExecute("update users set name=?, address=?", "Tom" , "China");
    Assert.assertEquals(1L,
        ctx.nQueryForObject("
    ctx.nExecute("delete from users where name=? or address=?", "Tom", "China");

SQL writing 4 - iXxxx() method, Inline style, write parameters directly in SQL, the parameters are temporarily stored in Threadlocal , SQL is automatically converted into preparedStatement when it is executed. The advantage of this method is that the assigned field and the actual parameter can be written on the same line, which is convenient for maintenance when the field is long, and it is also convenient for dynamic splicing of SQL according to uncertain conditions. This is a technological innovation that started with jSqlBox.

  ctx.iExecute("insert into users (", //
    " name ,", param0("Sam"), //Static introduction of SqlBoxContext.param0() method, the same
    as " address ", param("Canada"), //
    ") ", valuesQuesions());
  param0("Tom", "China");
  ctx.iExecute("update users set name=?,address=?");
  Assert.assertEquals(1L, ctx
    .iQueryForObject( "select count(*) from users where name=? and address=?" + param0("
  ctx.iExecute("delete from users where name=", question0("Tom"), " or address=", question("China"));

SQL写法5 - tXxxx()方法, 模板风格,利用模板来存放SQL变量:

    Map<String, Object> params=new HashMap<String, Object>();
    User sam = new User("Sam", "Canada");
    User tom = new User("Tom", "China");
    params.put("user", sam);
    ctx.tExecute(params,"insert into users (name, address) values(#{user.name},#{user.address})");
    params.put("user", tom);
    ctx.tExecute(params,"update users set name=#{user.name}, address=#{user.address}");
    params.clear();
    params.put("name", "Tom");
    params.put("addr", "China");
    Assert.assertEquals(1L,
        ctx.tQueryForObject(params,"select count(*) from users where name=#{name} and address=#{addr}"));
    params.put("u", tom);
    ctx.tExecute(params, "delete from users where name=#{u.name} or address=#{u.address}");

SQL写法6 - tXxxx()方法,模板风格和Inline风格的结合使用:

    user = new User("Sam", "Canada");
    put0("user", user);
    ctx.tExecute("insert into users (name, address) values(#{user.name},#{user.address})");
    user.setAddress("China");
    ctx.tExecute("update users set name=#{user.name}, address=#{user.address}" + put0("user", user));
    Assert.assertEquals(1L,
        ctx.tQueryForObject("select count(*) from users where ${col}=#{name} and address=#{addr}",
            put0("name", "Sam"), put("addr", "China"), replace("col", "name")));
    ctx.tExecute("delete from users where name=#{u. name} or address=#{u.address}", put0("u", user));

SQL writing 7 - tXxxx() method, still template style, but switch to use another template "NamedParamSqlTemplate" at runtime, Use colons as parameter delimiters.
jSqlBox is an open architecture design. It has default implementations for transactions, logs, and templates, but also supports switching to other third-party implementations.

    user = new User("Sam", "Canada");
    ctx.setSqlTemplateEngine(NamedParamSqlTemplate.instance());
    put0("user", user);
    ctx.tExecute("insert into users (name, address) values(: user.name,:user.address)");
    user.setAddress("China");
    ctx.tExecute("update users set name=:
    Assert.assertEquals(1L, ctx.tQueryForObject("select count(*) from users where ${col}=:name and address=:addr",
        put0("name", "Sam"), put("addr", "China"), replace("col", "name")));
    ctx.tExecute("delete from users where name=:u.name or address=:u.address", put0("u", user) );

SQL writing 8 - Data Mapper data mapping style, the same as the writing in Hibernate. From a performance point of view, jSqlBox strongly recommends that entity classes inherit from ActiveRecord classes, but jSqlBox also supports pure POJO classes:

    User user = new User("Sam", "Canada");
    ctx.insert(user);
    user .setAddress("China");
    ctx.update(user);
    User user2 = ctx.load(User.class, "Sam");
    ​​ctx.delete(user2);

SQL writing 9 - ActiveRecord style, the entity class inherited from the ActiveRecord class, automatically has CRUD methods such as insert/update/delete and a box() method that can be used to dynamically change the configuration at runtime. The origin of the jSqlBox project name is because of this box() method.

    System.out.println("=== ActiveRecord style ===");
    user = new User("Sam", "Canada");
    user.box().setContext(ctx);// set a SqlBoxContext to entity
    user.insert();
    user.setAddress("China");
    user.update();
    user2 = user.load("Sam");
    ​​user2.delete();

The following is a variant of ActiveRecord style, using this way of writing The premise is that the SqlBoxContext.setDefaultContext(ctx) method must be called when the program starts to set a global default context, which is suitable for single data source occasions. The advantage of this way of writing is that there is no shadow of the persistence layer tool in the business method:

    user = new User("Sam", "Canada").insert();
    user.setAddress("China");
    user.

    user2.delete();

SQL writing 10 - chain style, strictly speaking, this style should still be classified in ActiveRecord mode.

   One of the chain style:
   new User().put("id","u1").put("userName","user1").insert();
  
   The second chain style:
   new Address().put( "id","a1","addressName","address1","userId","u1").insert();
  
   chain style three:
   new Email().putFields("id","emailName", "userId");
   new Email().putValues("e1","email1","u1").insert();
   new Email().putValues("e2","email2","u1").insert ();  

Note: If there are a large number of insert and update statements, you can call the ctx.nBatchBegin() method before starting, and call ctx. nBatchEnd method, the insert method will be automatically aggregated into batch operations, but the premise of using this function is that the database supports the batch operation function (for example, MySQL should set rewriteBatchedStatements=true).
NoSQL query

The following is the NoSql query method in the jSqlBox project. For a set of relational database tables in the figure below, User and Role are in a many-to-many relationship, Role and Privilege are in a many-to-many relationship, and UserRole and RolePrivilege are two intermediate tables. , used to connect many-to-many relationships. If it is troublesome to perform multi-table related queries with ordinary SQL, jSqlBox includes a TinyNet sub-project, which is specially used to convert relational databases into in-memory graph structures, so that NoSql query methods can be used to browse queries between nodes.




NoSql query example 1: Find the permissions of two users u1 and u2

    TinyNet net = ctx.netLoad(new User(), new Role(), Privilege.class, UserRole.class, RolePrivilege.class);
    Set<Privilege> privileges = net.findEntitySet(Privilege.class,
        new Path("S-", User.class).where("id=? or id=?","u1","u2").nextPath("C-" , UserRole.class, "userId")
            .nextPath("P-", Role.class, "rid").nextPath("C-", RolePrivilege.class, "rid")
            .nextPath("P+", Privilege. class,

      System.out.print(privilege.getId()+" ");
     
    output result: p1 p2 p3

Description:

    The netLoad method will transfer all the contents of the table according to the given parameters, and spell it into the memory represented by the TinyNet instance like structure. Note that TinyNet is not thread-safe, so be careful when using it in a multi-threaded environment.
    A method similar to the netLoad method is the netLoadSketch method. Instead of loading the entire table, only all primary key and foreign key fields are loaded. This lazy loading can improve performance and save memory.
    Path is used to define a search path. The first parameter of the constructor consists of two letters. The first letter S means to search for the current node, P means to search for the parent node, and C means to search for the child node. The second letter "-" means that the intermediate result of the search is not included in the query result, "+" means the query result, and "*" means recursively search all nodes in the current path and put it in the query result. The second parameter defines the target object class, class instance or database table name, and the third parameter is a variable parameter, which is the associated attribute or associated column between the two targets (that is, the column name of a single foreign key or a composite foreign key) , for example the associated field between User and UserRole is "userId".
    The where() method of Path defines an expression to determine whether a node can be selected. The keywords supported by the expression are: all field names of the current entity, >, <, =, >, =, < Common string functions such as =, +, -, *, /, null, (), not, equals, contains, etc., see the manual or unit test source code for details.
    Path's nextPath() method is used to define the next search path.

NoSql query example 2 An automatic leapfrog query. The writing of Example 1 is rather long-winded. The following is the equivalent writing. Using the autoPath method, the search path can be automatically calculated, so as to achieve leapfrog query:

    TinyNet net = ctx.netLoad(new User(), new Role(), Privilege.class, UserRole.class, RolePrivilege.class);
    Set<Privilege> privileges = net.findEntitySet(Privilege.class,
        new Path(User.class ).where("id='u1' or id='u2'").autoPath(Privilege.class));
    for (Privilege privilege : privileges)
      System.out.print(privilege.getId()+" ");

Note that the autoPath method is only suitable for the case where the search path is unique. If there are multiple different paths from User to Privilege, the autoPath method cannot be applied, and the full path must be written.

NoSql query example 3 A Java native method judgment. Using Java's native method to judge nodes, you can use the familiar Java language and support the reconstruction of entity fields. The following example queries the permissions associated with Email "e1" and "e5":

  @Test
  public void testAutoPath2() {
    insertDemoData();
    TinyNet net = ctx.netLoad(new Email(), new User(), new Role(), Privilege.class,
        RolePrivilege.class);
    Set<Privilege> privileges = net.findEntitySet(Privilege.class,
        new Path(Email.class).setValidator(new EmailValidator()).autoPath(Privilege.class));
    for (Privilege privilege : privileges)
      System.out.println(privilege.getId());
    Assert.assertEquals(1, privileges.size());
  }

  public static class EmailValidator extends DefaultNodeValidator {
    @Override
    public boolean validateBean(Object entity) {
      Email e = (Email) entity;
      return ("e1".equals(e.getId()) || "e5".equals(e.getId())) ? true : false;
    }
  }

NoSql Example 4 Manual loading. The loading of TinyNet can also be manually loaded on demand, instead of using the netLoad method to load the entire table. For example, the following table runs two SQLs, queries the database twice and merges the results into a TinyNet instance:

    List<Map<String, Object> > mapList1 = ctx.nQuery(new MapListHandler(netProcessor(User.class, Address.class)),
        "select u.**, a.** from usertb u, addresstb a where a.userId=u.id");
    TinyNet net = ctx.netCreate(mapList1);
    Assert.assertEquals(10, net.size());

    List<Map<String, Object>> mapList2 = ctx.nQuery(new MapListHandler(),
        netConfig(Email.class) + "select e.** from emailtb as e");
    ctx.netJoinList(net, mapList2);
    Assert.assertEquals(15, net.size());

The writing of two asterisks in the above example is the only one in the jSqlBox project Where it breaks the standard SQL writing, it means querying the database table fields corresponding to all non-transient properties of the User object.

NoSql query example 5 A tree structure query, for the tree structure data table stored in Adjacency List mode, it is very convenient to use NoSQL query, such as the following database table:



query all child nodes of node B and node D (including nodes B and D themselves) :

    TinyNet net = ctx.netLoad(TreeNode.class);
    Set<TreeNode> TreeNodes = net.findEntitySet(TreeNode.class,
        new Path("S+", TreeNode.class).where("id=? or id=?" , "B", "D").nextPath("C*", TreeNode.class, "pid"));
    for (TreeNode node : TreeNodes)
      System.out.print(node.getId() + " ");
    Output: BDEFHIJKL

queries all parent nodes of F and K nodes (excluding F and K nodes themselves):

    TinyNet net = ctx.netLoad(TreeNode.class);
    Set<TreeNode> TreeNodes = net.findEntitySet(TreeNode.class,
        new Path("S-", TreeNode.class).where("id=' F' or id='K'").nextPath("P*", TreeNode.class, "pid"));
    for (TreeNode node : TreeNodes)
      System.out.print(node.getId() + " ");
    Output: BHAD

For large trees with massive data, it is unrealistic to use the netLoad method to transfer the entire table to the memory and then use the NoSQL method to query At this time, the recursive query function of the database can be used to find a subtree into the memory and then query by the NoSQL method. If the database does not support recursion or has performance problems, you can also refer to the third scheme of the infinite depth tree scheme invented by me (see http://drinkjava2.iteye.com/blog/2353983 ), you can query the origin with only one SQL statement The desired subtree is loaded into memory.

By the way, finally, some people may have noticed that in the above example, where are the essential configurations for other ORM tools such as one-to-many and many-to-many? The answer is: remember the dynamic configuration at the beginning In the example demonstration, is there such a sentence: u.tableModel().fkey().columns("teamId").refs("team", "id"); This is a many-to-one configuration, and there is an FKey annotation It also does the same thing, which means adding a database foreign key to the teamId column of the user database table, referring to the id primary key field of the team table. A foreign key is a relationship, which is why a relational database is called a relational database. The relational database only has the unique association of "many-to-one", so there is no many-to-many and one-to-many configuration in jSqlBox, which is completely based on traditional ER modeling. jSqlBox supports composite primary keys and composite foreign keys (see the jDialects project for details). Programmers can freely add or delete the foreign key constraint configuration of entities in the program during runtime (the configuration of each entity is a copy of the fixed configuration, as long as the toCreateDDL method is not called to generate and execute the script, it has no effect on the actual database ), so that you can freely create associations between objects, which can then be queried in NoSQL fashion. Another point: NoSql queries are browsed between different types of nodes, so the returned results can also be different entity classes, which will not be demonstrated here due to space reasons.

The above is the initial introduction of jSqlBox. You are welcome to try it out and make mistakes. At the same time, you are also welcome to join the development team. PDF manual is in production. If you are interested in trying it out, you can refer to the introduction of this article and the unit test source code to understand this project.
Appendix - Performance Testing.

The following is a performance test of 10,000 CRUD operations performed by jSqlBox with different SQL writing methods (unit test source code SpeedTest.java), running on H2 memory database (i3 2.4G), which can exclude the impact of disk read and write, reflecting the framework itself performance. From the test results, it can be seen that various writing methods are basically similar to pure JDBC except that the template method is a bit slow.

The Compare Method Execute Time for REPEAT 10000 Times:
                    pureJdbc: 0.983 S
       dbUtilsWithConnMethod: 0.890 S
         dbUtilsNoConnMethod: 0.968 S
               nXxxJdbcStyle: 0.953 S
             iXxxInlineStyle: 1.248 S
           tXxxTemplateStyle: 3.714 S
  tXxxTemplateAndInlineStyle : 3.589 S
tXxxNamingParamTemplateStyle: 3.730 S
             dataMapperStyle: 2.91 S
           activeRecordStyle: 1.687 S
  activeRecordDefaultContext : 1.702 s

This test does not include NoSQL queries, because NoSql currently has only query methods in this project, but no writing methods, so no comparison test is added. But NoSql does not mean low performance, because it is a pure memory query, and in jSqlBox, when there are repeated and fixed queries between nodes, a cache will be formed between nodes, and the query performance may be better than ordinary SQL queries. It is much higher (currently, the query cache of ordinary SQL in the jSqlBox project has not been developed).

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326177166&siteId=291194637