Lealone ORM 是一个简洁优雅的类型安全的 ORM 框架,支持链式(或流式) API,能杜绝 SQL 注入。

在建表时,Lealone ORM 可以为每个表自动生成对应的模型类代码,这些代码不需要应用开发人员修改维护。

应用开发人员负责编写的代码无需导入 Lealone ORM 的任何东西(比如类、接口、注解)。

下文通过一个例子来介绍 Lealone ORM 大多数常用的功能,比如单表查添删改操作、多表关联查询、事务操作等等。

该例子创建了 customer 和 order 两张表,它们两者存在关联关系。

文章最后有完整的 Java 代码。

1. 建表

  1. set @packageName 'org.lealone.examples.orm.generated'; -- 生成的模型类所在的包名
  2. set @srcPath './src/main/java'; -- 生成的模型类对应的源文件所在的根目录
  3. -- 创建customer表,会生成一个名为Customer的模型类
  4. create table if not exists customer (
  5. id long primary key,
  6. name char(10),
  7. notes varchar,
  8. phone int
  9. ) package @packageName generate code @srcPath;
  10. -- 创建order表,会生成一个名为Order的模型类
  11. -- order是关键字,所以要用特殊方式表示
  12. create table if not exists `order` (
  13. customer_id long,
  14. order_id int primary key,
  15. order_date date,
  16. total double,
  17. FOREIGN KEY(customer_id) REFERENCES customer(id)
  18. ) package @packageName generate code @srcPath;

这一步用于创建 customer 和 order 表,加了新的扩展语法, 如果指定 generate code,会生成 Customer 和 Order 两个模型类:

  1. public class Customer extends Model<Customer> {
  2. public static final Customer dao = new Customer(null, ROOT_DAO);
  3. public final PLong<Customer> id;
  4. public final PString<Customer> name;
  5. public final PString<Customer> notes;
  6. public final PInteger<Customer> phone;

生成的 Customer 模型类的代码是不用修改的,采用的是一种简化的充血模型。

可以基于模型类的字段来构建出跟普通 SQL 极其相似的类型安全的 DSL。

因为 customer 和 order 两张表存在关联关系,所以生成的代码也体现了这种关系:

  1. public Customer addOrder(Order m) {
  2. m.setCustomer(this);
  3. super.addModel(m);
  4. return this;
  5. }
  6. public Customer addOrder(Order... mArray) {
  7. for (Order m : mArray)
  8. addOrder(m);
  9. return this;
  10. }
  11. public List<Order> getOrderList() {
  12. return super.getModelList(Order.class);
  13. }

应用开发人员不需要去了解 Customer 模型类的代码实现细节, 只需要通过 IDE 的智能提示功能看看它提供了哪些可用字段和方法即可。

2. 单表 crud 操作

2.1 insert

  1. Customer c = new Customer();
  2. c.id.set(1001);
  3. c.name.set("rob");
  4. c.phone.set(12345678);
  5. c.insert();

或者使用更简洁的链式风格:

  1. new Customer().id.set(1001).name.set("rob").phone.set(12345678).insert();

自动生成的 Customer 类不是 POJO,所以并没有使用常规的 setter/getter 方法, 而是用 c.name.set(“rob”) 替换 c.setName(“rob”),这样的设计是有特别考量的。

Customer 类继承自 org.lealone.orm.Model,因为继承有先天的缺陷, 比如将来在 Model 这个父类中新增一些方法就有可能跟它的子类出现方法名冲突, 所以 Customer 类这样的 Model 子类使用 final 字段比使用 setter/getter 更安全,Model 类将来更容易扩展和维护。

除了继承原因外,使用 final 字段更灵活,比如可以使用 c.name.startsWith(“rob”) 作为查询条件,可以扩展出无数的方法。

2.2 find

  1. // find one
  2. Customer c = Customer.dao.where().id.eq(1001).findOne();
  3. // find list
  4. List<Customer> list = Customer.dao.id.eq(1001).findList();

在生成的模型类代码里面会有一个 static final 的名为 dao 的字段,通过 dao 来构建链式风格的查询语句。

where() 是可选的,加上去能让代码更易读,一看跟在它后面的调用代码就知道都是用来构建查询条件的。

2.3 update

  1. // 单记录 update
  2. c.notes.set("test").update();
  3. // 批量 update
  4. Customer.dao.notes.set("batch update").where().name.startsWith("rob").update();

单记录 update 是针对通过 find 方法返回的实例的,批量 update 需要通过 dao 来构建 update 语句。

2.4 delete

  1. // 单记录 delete
  2. c.delete();
  3. // 批量 delete
  4. Customer.dao.where().name.startsWith("rob").delete();

跟 update 一样,单记录 delete 也是针对通过 find 方法返回的实例的,批量 delete 需要通过 dao 来构建 delete 语句。

2.5 分页查询

  1. // count
  2. int count = dao.findCount();
  3. // offset、limit
  4. List<Customer> list = dao.offset(0).limit(1).findList();

注意,offset 是从0开始的。

2.6 JSON

  1. Customer c = new Customer();
  2. c.name.set("rob");
  3. c.phone.set(12345678);
  4. String json = c.encode(); //编码为 JSON 字符串
  5. c = Customer.decode(json); //从 JSON 字符串解码出 Customer 对象

3. 关联查询

3.1 批量增加有关联的记录

  1. Order o1 = new Order().orderId.set(2001).orderDate.set("2018-01-01");
  2. Order o2 = new Order().orderId.set(2002).orderDate.set("2018-01-01");
  3. Customer customer = new Customer().id.set(1002).name.set("customer1");
  4. customer.addOrder(o1, o2).insert();
  5. // 调用addOrder后,Order的customerId字段会自动对应Customer的id字段
  6. assertEquals(o1.customerId.get(), customer.id.get());

3.2 两表join

  1. Customer c = Customer.dao;
  2. Order o = Order.dao;
  3. customer = c.join(o)
  4. .on().id.eq(o.customerId)
  5. .where().id.eq(1002)
  6. .findOne();
  7. // 一个customer对应两个Order
  8. List<Order> orderList = customer.getOrderList();
  9. assertEquals(2, orderList.size());
  10. assertTrue(customer == orderList.get(0).getCustomer());

Lealone ORM 框架实现关联查询时不会像其他 ORM 框架那样产生 N + 1 问题, 会根据查询语句一次性把满足条件的记录查找出来,然后再创建所有的模型类的实例。

4. 事务

  1. try {
  2. Customer.dao.beginTransaction(); // 开始一个新事务
  3. new Customer().id.set(1003).name.set("rob3").insert();
  4. new Customer().id.set(1004).name.set("rob4").insert();
  5. Customer.dao.commitTransaction(); // 提交事务
  6. } catch (Exception e) {
  7. Customer.dao.rollbackTransaction(); // 回滚事务
  8. e.printStackTrace();
  9. }

事务默认采用的是自动提交模式,调用 beginTransaction() 后事务变成手动提交模式。

5. 完整例子

下载项目 lealone-orm-demo

源码编译与打包请执行 build -p

运行执行 build -r

6. 可能出现的问题

如果执行 build -p 找不到 Lealone 的依赖包, 需要下载以下项目的代码:

lealone-database

执行 build -i 把它们安装到本地的maven仓库即可。

7. 更多例子

请看 orm unit test

8. 完整的代码例子

  1. package org.lealone.examples.orm;
  2. import static org.junit.Assert.assertEquals;
  3. import static org.junit.Assert.assertTrue;
  4. import java.sql.Connection;
  5. import java.sql.DriverManager;
  6. import java.sql.Statement;
  7. import java.util.List;
  8. import org.lealone.examples.orm.generated.Customer;
  9. import org.lealone.examples.orm.generated.Order;
  10. public class OrmDemo {
  11. public static void main(String[] args) throws Exception {
  12. createTable();
  13. testCrud();
  14. testJoin();
  15. testTransaction();
  16. }
  17. // ------------ 以下代码中出现的的 where()都是可选的 ------------
  18. // 单表crud操作
  19. static void testCrud() {
  20. // insert
  21. new Customer().id.set(1001).name.set("rob").phone.set(12345678).insert();
  22. Customer dao = Customer.dao;
  23. // find one
  24. Customer c = dao.where().id.eq(1001).findOne();
  25. assertEquals("rob", c.name.get());
  26. // find list
  27. List<Customer> list = dao.id.eq(1001).findList();
  28. assertEquals(1, list.size());
  29. // count
  30. int count = dao.findCount();
  31. assertEquals(1, count);
  32. // 单记录 update
  33. c.notes.set("test").update();
  34. c = dao.where().id.eq(1001).findOne();
  35. assertEquals("test", c.notes.get());
  36. // 批量 update
  37. dao.notes.set("batch update").where().name.startsWith("rob").update();
  38. c = dao.id.eq(1001).findOne();
  39. assertEquals("batch update", c.notes.get());
  40. // 分页查询
  41. list = dao.offset(0).limit(1).findList();
  42. assertEquals(1, list.size());
  43. // delete
  44. dao.where().id.eq(1001).delete();
  45. count = dao.findCount();
  46. assertEquals(0, count);
  47. }
  48. // 两表关联查询
  49. static void testJoin() {
  50. // 批量增加有关联的记录
  51. Order o1 = new Order().orderId.set(2001).orderDate.set("2018-01-01");
  52. Order o2 = new Order().orderId.set(2002).orderDate.set("2018-01-01");
  53. Customer customer = new Customer().id.set(1002).name.set("customer1");
  54. customer.addOrder(o1, o2).insert();
  55. // 调用addOrder后,Order的customerId字段会自动对应Customer的id字段
  56. assertEquals(o1.customerId.get(), customer.id.get());
  57. // 关联查询
  58. Customer c = Customer.dao;
  59. Order o = Order.dao;
  60. customer = c.join(o).on().id.eq(o.customerId).where().id.eq(1002).findOne();
  61. // 一个customer对应两个Order
  62. List<Order> orderList = customer.getOrderList();
  63. assertEquals(2, orderList.size());
  64. assertTrue(customer == orderList.get(0).getCustomer());
  65. }
  66. // 测试事务
  67. static void testTransaction() {
  68. try {
  69. Customer.dao.beginTransaction(); // 开始一个新事务
  70. new Customer().id.set(1003).name.set("rob3").insert();
  71. new Customer().id.set(1004).name.set("rob4").insert();
  72. Customer.dao.commitTransaction(); // 提交事务
  73. } catch (Exception e) {
  74. Customer.dao.rollbackTransaction(); // 回滚事务
  75. e.printStackTrace();
  76. }
  77. int count = Customer.dao.where().id.eq(1003).or().id.eq(1004).findCount();
  78. assertEquals(2, count);
  79. }
  80. // 执行建表脚本,同时自动生成对应的模型类的代码
  81. static void createTable() throws Exception {
  82. String jdbcUrl = "jdbc:lealone:embed:test";
  83. System.setProperty("lealone.jdbc.url", jdbcUrl);
  84. try (Connection conn = DriverManager.getConnection(jdbcUrl); Statement stmt = conn.createStatement()) {
  85. stmt.executeUpdate("RUNSCRIPT FROM './src/main/resources/tables.sql'");
  86. }
  87. }
  88. }