SpringBoot-NutzDao

x33g5p2x  于2021-09-25 转载在 Spring  
字(38.3k)|赞(0)|评价(0)|浏览(355)

SpringBoot-NutzDao

注意使用的是 1.r.66

环境搭建

Maven

<properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <!-- UTF8大法好 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.4.1</version>
        </dependency>

        <!--Springboot整合Nutz-->
        <dependency>
            <groupId>org.nutz</groupId>
            <artifactId>nutz-plugins-spring-boot-starter</artifactId>
            <version>1.r.66</version>
        </dependency>
        <!--阿里的数据连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.9</version>
        </dependency>
        <!--数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--spring-boot测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

log4j.properties

log4j.rootLogger=debug,Console

log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%-5p] %d{HH:mm:ss.SSS} %l - %m%n

application.yml

server:
  #设置程序启动端口号
  port: 7000
spring:
  aop:
    #开启aop代理
    auto: true
    proxy-target-class: true
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1:3306/nutzdemo?characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false&maxReconnects=10&useSSL=false
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # 初始连接数
      initial-size: 5
      # 最大激活数
      max-active: 50
      # 最大等待时间
      max-wait: 3000
      # 是否启用非公平锁
      use-unfair-lock: true
      # mysql 用 false | oracle 用 true
      pool-prepared-statements: false
nutz:
  json:
    auto-unicode: false
    quote-name: true
    ignore-null: true
    null-as-emtry: true
    enabled: true
    mode: compact
  dao:
    runtime:
      create: true #自动创建表
      migration: false #根据bena自动更新表结构
      basepackage: com.xslde.model.mapped  #扫描bean
    sqlmanager:
      paths:
        - sqls  #sql文件存放位置

AppNutz(启动类)

@SpringBootApplication
public class AppNutz {
    public static void main(String[] args) {
        SpringApplication.run(AppNutz.class,args);
    }
}

AppNutzTest测试启动类

import com.zutz.AppNutz;
import com.zutz.pojo.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.nutz.dao.Dao;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppNutz.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class AppNutzTest {
    @Resource
    private Dao dao;

}

使用Nutz开发持久层(DAO)

NutzDao 和 Mybitis , JPA … 这些持久层框架一样 好处就是简单简单在简单自行体会

入门Dome

创建数据库 以mysql为例

create database nutzdemo;

创建POJO

package com.zutz.pojo;

import lombok.Data;
import org.nutz.dao.entity.annotation.*;

import java.io.Serializable;

@Table("t_person")   // 声明了Person对象的数据表
@Data
public class Person implements Serializable {

    @Id       // 表示该字段为一个自增长的Id,注意,是数据库表中自增!!
    private int id; // @Id与属性名称id没有对应关系.

    @Name    // 表示该字段可以用来标识此对象,或者是字符型主键,或者是唯一性约束(内容不能重复)
    private String name;

    @Column      // 表示该对象属性可以映射到数据库里作为一个字段
    private int age;

}

在AppNutzTest测试类中添加一个方法

//插入一条语句
    @Test
    public void show(){

        //根据实体类自动创建表 false=如果表存在了就跳过
        dao.create(Person.class,false);
        Person p = new Person();
        p.setName("ABC");
        p.setAge(20);
        dao.insert(p); //插入数据到数据库里
    }
  • 执行完毕后,在数据表中会多出一条记录。
  • 这个例子足够作为 Hello World 了, 祝你玩的开心 😃

Dao 接口的基本操作

Pet实体类

package com.zutz.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.entity.annotation.Table;

import java.io.Serializable;

@Table("t_pet")   // 声明了 pet对象的数据表
@Data
@NoArgsConstructor
public class Pet implements Serializable {

    @Id       // 表示该字段为一个自增长的Id,注意,是数据库表中自增!!
    private int id; // @Id与属性名称id没有对应关系.

    @Name    // 表示该字段可以用来标识此对象,或者是字符型主键,或者是唯一性约束(内容不能重复)
    private String name;

    @Column      // 表示该对象属性可以映射到数据库里作为一个字段
    private int age;

    public Pet(String name,int age){
        this.name=name;
        this.age=age;
    }
    public Pet(int age){
        this.age=age;
    }

}

创建表和插入测试数据

dao.create(Pet.class, false);
        Pet pet1=new Pet("hu1",33);
        Pet pet2=new Pet("hu2",33);
        Pet pet3=new Pet("hu3",33);
        Pet[] pets ={ pet1,pet2,pet3};
        dao.insert(pets);

概述

传统关系型数据库定义了四种数据操作:

  1. 插入 Insert
  2. 删除 Delete
  3. 更新 Update
  4. 查询 Query

可以说,这四种操作涵盖了所有的数据操作。并且,除了 插入 操作,所有的操作都是可以一次针对多条记录的。

但是,Nutz.Dao 认为从使用者的角度来看,这四种操作还是有所不同的。比如,查询返回的结果,很多时候仅仅是一条记录。 我们需要为这种情况进行优化。所以,Nutz.Dao 在传统关系型数据库数据操作的基础上定义了如下的数据操作:

插入Insert一条 SQL 插入一条记录或者多条记录
插入FastInsert一条 SQL ,通过batch插入多条记录
删除Delete一条 SQL 删除一条记录
更新Update一条 SQL 更新一条或者多条记录
获取Fetch一条 SQL 获取一条记录
查询Query一条 SQL 根据条件获取多条记录
清除Clear一条 SQL 根据条件删除多条记录
建表Create根据实体建表
删表Drop根据实体/表名称进行删表
聚合Func执行sum,count等操作

请注意: 这里我是说 “一条” SQL。 如果通过 Dao 接口,你传入的是一个集合或者数组,它会为每一个元素 都生成一条 SQL 并执行,并更新操作:

Pet[] pets = xxxx;
dao.update(pets);    // 可以是数组,当然 pets 也可以是集合 同理delete 和 insert 也支持传入数组和集合
创建数据表

为 Pet 创建数据表,如果数据表存在,则自动忽略

//一般我们都这样写
dao.create(Pet.class, false);
删除数据表

删除 Pet 表

dao.drop(Pet.class);//删除Pet表 ,慎用!!
插入 Insert
Person p = new Person();
p.setName("Peter");
p.setAge(22);
dao.insert(p);

因为我们在创建表的时候默认创建的id列就是自增加 所以不用指定ID也可以

快速插入

会将多个 insert 插入语句拼接为一条一起执行

dao.fastInsert(pets) ;
查询一条数据

根据名称获取 (如果你的实体声明了 @Name 字段, 字符型主键,或者带唯一性索引的字段)

Pet p= dao.fetch(Pet.class, "hu1");
System.out.println(p);

根据 ID 获取 (如果你的实体声明了 @Id 字段, 数值型主键)

Person p = dao.fetch(Pet.class,2);
System.out.println(p);

@Id和@Name可以同时存在于一个Pojo类内,但不允许标注在同一个属性,毕竟不可以同时是数值型主键又是字符型主键

查询多条数据

查询全部记录

List<Pet> people = dao.query(Pet.class, null);
System.out.println(people);

按条件查询

List<Pet> people = dao.query(Pet.class, Cnd.where("name", "like", "h%"));
System.out.println(people);
  • Cnd 类的全名是 org.nutz.dao.Cnd

  • 它主要是用来快速替你建立一个 org.nutz.dao.Condition 接口的实现类

  • where() 函数 第一个参数是字段名,要和 Java 类里面的字段名相同。

  • where() 函数 第二个参数遵循 SQL 的标准,可以是 >, <, >=, <= 等等

  • 提供了一个 wrap 函数,你可以直接写 SQL 的条件

  • 如果你愿意,你完全可以自己实现一个 Condition,来做更复杂灵活的判断

  • 关于更多的查询条件的说明,请参看 复杂条件

分页查询

List<Pet> people = dao.query(Pet.class, Cnd.where("age", ">", 18), dao.createPager(1, 2));
System.out.println(people);
  • dao.createPager 第一个参数是第几页,第二参数是一页有多少条记录
  • 关于分页更多的说明,请参看 [分页查询](
更新 Update

注意, 实体类至少带@Id/@Name/@Pk中的一种

Person p = dao.fetch(Person.class,2);
p.setAge(32);
dao.update(p)
dao.update(list); //更新一个集合也是可以的
按条件更新
// 根据特定条件更新特定字段
  dao.update(Pet.class, Chain.make("name","huanmin1"), Cnd.where("id","=",1));

// 常用的+1更新
dao.update(Pet.class, Chain.makeSpecial("age", "+1").add("name", "huanmin2"), Cnd.where("id","=", "2"));
删除 Delete

Pet必须带@Id/@Name/@Pk中的一种或多种

根据名称删除 (如果你的实体声明了 @Name 字段). 批量删除请用clear

dao.delete(Pet.class,"hu1");

根据 ID 删除 (如果你的实体声明了 @Id 字段)

dao.delete(Pet.class,2);

直接删列表.

List<Pet> pets=new ArrayList<>();
        pets.add(new Pet(1));
        pets.add(new Pet(2));
        dao.delete(pets);

如果要按条件删,用dao.clear 下面有教程

  • http://www.nutzam.com/core/dao/pager.html)
清除 Clear

清除所有记录

dao.clear(Pet.class); //还是那句,慎用

按条件清除

dao.clear(Pet.class, Cnd.where("id", "=", 14));
  • 关于更多的清除条件的说明,请参看 复杂条件
插入和更新集合

无论是插入 (Insert) 还是更新 (Update),你传入的对象都可以不仅仅是一个 POJO,你可以传入:

  • 集合 ( Collection<?> )
  • Map<?,?>
  • 数组 ( T[] )

Nutz.Dao 会自动替你拆包,对集合成员依次执行相应操作。 对于 Map,它会迭代每一个值。

聚合操作(func)

sum(计算和)

int sum = dao.func(Pet.class, "sum", "age");
        System.out.println( sum);

avg(计算平局数)

int sum = dao.func(Pet.class, "avg", "age");
        System.out.println( sum);

其他类型

最小值

dao.func2(Person.class, "min", "price");

最大值

int min= (int)dao.func2(Pet.class, "min", "id");
        System.out.println( min);

其他的自己找,数据库有的都能使用

自动建表

部署到生产环境的时候需要导入SQL文件,是否觉得很繁琐? 而且还得为不同的数据库准备不同的SQL脚本? 太麻烦了是不是.

如果能自动建表,自动迁移表结构,然后导入初始化数据,是不是部署过程就非常容易了呢?? 想想就有点小激动.

Dao接口有一个create方法,通过它可以让nutz为你建好数据库表

dao.create(Pet.class, false);

第一个参数是Pojo类, 第二个参数是如果表存在 false=表存在就不创建 true=表存在先删除在创建

批量建表,扫描某个package下的bean,为带@Table注解的类建表

Daos.createTablesInPackage(dao, "net.wendal.nutzbook.bean", false);

表结构自动迁移

老板说要每个用户新增加个phone字段,咋搞? 自定义SQL执行一条ALTER吗? 不不不,自动迁移一下表结构就好了.

// 单个迁移
Daos.migration(dao, User.class, true, false, false); // 新增字段true,删除字段false,检查索引false

//额,那很多很多pojo呢? 也能批量迁移吗? 可以的

// 批量迁移
Daos.migration(dao, "net.wendal.nutzbook.bean", true, false, false);

自动建表的字段配置

建表时的详细配置,大部分通过@ColDefine实现

定义字符串长度,例如1024,你懂的

@ColDefine(width=1024)
private String name;

定义浮点数精度

@ColDefine(width=10,precision=2, type=ColType.FLOAT) // 对数据库来说没有float和double的区别
private double price;

不允许为null

@ColDefine(notNull=true, width=128)
private String nickname;

建表时的详细配置,大部分通过@ColDefine实现

定义字符串长度,例如1024,你懂的

@ColDefine(width=1024)
private String name;

定义浮点数精度

@ColDefine(width=10,precision=2, type=ColType.FLOAT) // 对数据库来说没有float和double的区别
private double price;

不允许为null

@ColDefine(notNull=true, width=128)
private String nickname;

自动建表的索引配置

除了为@Id/@Name/@Pk建立自动索引之外, 通过@TableIndexes注解可以配置更多自定义索引.

例如为name和age建立联合索引

@Table("t_user")
@TableIndexes({@Index(name="name_age", fields={"name", "age"}, unique=false)})

自1.r.63起,索引的名称可以自动帮你生成,上面的例子,如果name不填的话,默认"IX_TableName_name_age",及"前缀_表名_字段名" 其中,唯一索引前缀为"UX_",非唯一索引前缀为"IX_"

局限性

  • 不生成外键,我们也不推荐用外键
  • 只能解决一般建表需求,复杂的表结构请通过自定义sql完成

关于主键

简要介绍

为使用 Dao 接口的 fetch(Class<?>, long) 以及 fetch(Class<?>, String),需要为一个 POJO 指明它的主键。 主键可以是整数型的,也可以是字符型的。同时它也可以支持复合主键。

类型注解位置
整数型主键注解 @Id声明在字段上
字符型主键注解 @Name声明在字段上
复合主键注解 @PK声明在类上

注意: 对于一个 POJO,你可以同时为其声明 @Id 和 @Name,它们都能正常工作。你只需要保证 @Name 对应的字段在数据库里有唯一性约束即可。 但是通常, Nutz.Dao 并没有假设你同时在一个 POJO 里应用 @Id, @Name 和 @PK,如果你 这么做了,可能会引发一些奇怪的问题。事实上,你也不可能这么做,不是吗?

TIPS:注解 @Id 与注解 @Name 声明的字段不需要另外加上注解 @Column; 在注解 @PK 里面声明的对应复合主键的字段不需要另外加上注解 @Column。

海域 @id 和@Name 在类中只能各使用一次

整数型主键

@Id与主键属性名称id没有强制性,只是惯例叫id,你可以起名为abc的.

@Table("t_pet")
public class Pet{
    @Id  //只允许一个
    private int id;
    ...

默认Nutz.Dao 认为一个整数型主键,默认就是自增的。

由于默认的,@Id 字段被认为是自增的,所以在插入时,Nutz.Dao 会忽略这个字段。但是,有些时候,你的整数主键不是自增的, 你希望手工为其设值,怎么办呢? 你可以给 @Id 设一个属性: auto=false

@Table("t_pet")
public class Pet{
    @Id(auto=false)
    private int id;
    ...

Nutz.Dao 在插入对象时,就不会忽略你这个主键的值了。

字符型主键(也就是唯一性约束内容不能重复)

@Table("t_pet")
public class Pet{
    @Name    //只允许一个
    private String name; // 属性名称可任意指定,并非强制叫name
    ...

忽略大小写

@Table("t_pet")
    public class Pet{
        @Name(casesensitive=false)
        private String name;
        ...

复合主键

@Table("t_pet")
@PK( {"masterId", "petId"} )
public class Pet{
    private int masterId
    private int petId;
    ...

主键生成器

不想通过数据库自动生成 1 2 3 4 5 … 这种顺序 id 我们可以通过uuid自动生成主键

@Name //注意,字符串主键用@Name,与属性名称无关!!!
@Prev(els=@EL("uuid(32)")) // 可以是 uuid() uuid(32)
private String id;

还可以调用当前pojo内自己写的方法生成主键

@Id(auto=false)
@Prev(els=@EL("$me.nextId()"))
private int id;

public int nextId() {
    return System.currentTimeMillis();//仅供演示!!!
}

复杂的SQL条件

概述

什么是 Nutz.Dao 中的复杂SQL条件

  • 对于 Nutz.Dao 来说,它本质上就是将你的 Java 对象转化成 SQL,然后交给 JDBC 去执行。
  • 而 SQL 中,当执行数据删除和查询操作时,最常用的就是 WHERE 关键字。
  • WHERE 关键字后面的就是所谓的复杂查询条件

Nutz.Dao 将如何如何使用这个条件

  • Dao 接口的 clear 方法和 query 方法的第二个参数,就是为了生成 WHERE 后面那段字符串设计的
  • 这个参数是一个 org.nutz.dao.Condition 接口的实现类
  • 通过该接口的 toSql(org.nutz.dao.entity.Entity) 方法, Nutz.Dao 将获得 WHERE 后面那段字符串
  • 当然也包括 ORDER BY

Nutz 给你的快速实现

  • 如果你的数据库字段被假设不会发生变化,用直接硬编码是个很好的选择
  • 如果在开发期,你的数据库字段变化非常频繁,用 Cnd 工具类则是更好的选择

一个友好的工具类 – Cnd

有些情况,数据库中的字段同 Java 对象中的字段并不同名, 所以就需要给 Java 字段上的数据库字段注解加上参数 @Column(“数据库字段名”) 如果你通过 Cnd.wrap() 硬编码某个字段,那么当这个字段数据库字段名发生改变时,你就需要改动很多。 因此你希望仅仅将对于数据库的变动限制在 Java 对象的源文件里 所以 Nutz 提供了 Cnd.where() 方法

Condition c = Cnd.where("age",">",30).and("name", "LIKE", "%K%").asc("name").desc("id");

这个条件将生成 SQL

WHERE age>30 AND name LIKE '%K%' ORDERBY name ASC, id DESC

你也可以嵌套表达式

SqlExpressionGroup e1 = Cnd.exps("name", "LIKE", "P%").and("age", ">", "20");
SqlExpressionGroup e2 = Cnd.exps("name", "LIKE", "S%").and("age", "<", "30");
Condition c = Cnd.where(e1).or(e2).asc("name");

这个条件将生成 SQL

WHERE (name LIKE 'P%' AND age>'20') OR (name LIKE 'S%' AND age<'30') ORDER BY name ASC

直接硬编码(不推荐)

最暴力的方法就是直接输出 WHERE 关键字后面的 SQL 代码了。比如查询一个 Person 对象

List<Person> crowd = dao.query(Person.class, Cnd.wrap("name LIKE 'J%' AND age>20"), null);

部分暴力,使用Static

// 筛选年龄(age)小于20,现金(cash字段)多于负债(due字段)的XX
List<Pet> list = dao.query(Girl.class, Cnd.where("age", "<", 20).and(new Static("cash > due")));

拼装更加复杂的条件

上面的例子的 Cnd.where 函数,在大多数情况下可以快速的生成一个简单的查询条件。但是,如果查询条件非常复杂, 用它可能就比较费劲了。是的,它的设计初衷就是 “查询条件应该一行搞定”。

有些时候,查询条件很复杂,一行确实搞不定,怎么办?Nutz-1.b.38 以后,提供了 Criteria 接口,它继承自 Condition 接口,它的设计目的有两个:

  1. 让程序员更容易的拼装复杂逻辑的条件
  2. 让生成的 SQL 可以被参数化,更好的支持 PreparedStatement

这个接口的使用也很简单,它基本符合 “IDE 的所见即所得” 接口设计原则。 就是说,如果你的 IDE 有智能提示的话, 你使用这个接口是不需要文档的。

// 创建一个 Criteria 接口实例
Criteria cri = Cnd.cri();

// 组装条件
if(...){
    cri.where().andIn("i"d", 3,4,5).andIn("name", "Peter", "Wendal", "Juqkai");
}else if(...){
    cri.where().andLT("id", 9);
}else if(...){
    cri.where().andInBySql("关联字段","select id from 关联表 where name = '%s'",变量);
}else if(...){
    cri.where().andInBySql("关联字段","select id from 关联表 where name like '%%%s%%'",变量);
}

if(...){
    cri.where().andLike("name", "%A%");
}

cri.getOrderBy().asc("name").desc("id");

// 执行查询
List<MyObj> list = dao.query(MyObj.class, cri, null);

注意: 如果是使用in 传入的参数可以是数组或者是集合都行 会自动解析

Criteria 的 where() 函数返回的是 SqlExpressionGroup,主要由它来提供各种 SQL 条件的组合方法。 这里需要给出一点提示,比如方法名 andGT,表示的是 andGreatThan,即 “大于” 的意思,同理:

  • LT : 小于 (LessThan)
  • GTE : 大于等于 (GreatThanEqual)
  • LTE : 小于等于 (LessThanEqual)

模糊查询的小提示

正确的写法是

Cnd cnd = Cnd.where("name", "like", "%" + str + "%s");

分页查询

概述

使用数据库的应用程序,多数情况下都需要使用 “分页” 这个功能。尤其是在 Web 应用程序中,后端的分页查询尤其的普遍。 在以往的使用经验中,一个分页查询,除了能获取到一个列表外,我们通常需要如下几个信息才能在客户端显示出一个完整的翻页条。

  • 当前页数 – 第几页
  • 页大小 – 每页有多少条记录
  • 总页数 – 一共多少页
  • 总记录数 – 如果不分页,一共有多少条记录

当我们获得了这四条信息后,对于维护一个翻页查询就足够。

Nutz.Dao 的查询接口天然就支持分页查询。

Dao 接口的第三个参数

让我们先看看 Nutz.Dao 接口查询函数的声明:

<T> List<T> query(Class<T> classOfT, Condition condition, Pager pager);

这个接口有三个参数

  • classOfT 告诉 Nutz.Dao 需要查询的实体类型
  • condition 告诉 Nutz.Dao 查询出的列表需要符合的条件。详细请看 复杂条件
  • 最后一个参数,就是告诉 Nutz.Dao 将结果如何分页的了。

Pager 对象有如下几个注意事项:

  • 如果 pager 被传入了 null,则不分页
  • 生成 Pager 对象的时候需要传入 “当前页数” 和 “页大小”
  • Pager 虽然有 getRecordCount() 和 getPageCount() 方法,但是它不会自动被设值 – 因为考虑到效率
  • 通过 Pager.setRecordCount() 可以为 Pager 设置结果集的总数,Pager 会通过 getPageCount() 返回总页数
  • 分页页数从1开始算,如果页数是0,代表不分页,请特别注意.

将分页信息和查询结果一起返回 (下面两种方法 带条件的和不带条件的)

public QueryResult getPetList(Dao dao, int pageNumber, int pageSize){
        Pager pager = dao.createPager(pageNumber, pageSize);
        List<Pet> list = dao.query(Pet.class, null, pager);
        pager.setRecordCount(dao.count(Pet.class));
        return new QueryResult(list, pager);
    }

    public QueryResult getPetList(Dao dao, int pageNumber, int pageSize ,Condition condition){
        Pager pager = dao.createPager(pageNumber, pageSize);
        List<Pet> list = dao.query(Pet.class, condition, pager);
        pager.setRecordCount(dao.count(Pet.class));
        return new QueryResult(list, pager);
    }

使用

Condition   condition= Cnd.where("age",">",30);
        QueryResult petList = getPetList(dao, 1, 2, condition);
        System.out.println("配置信息"+petList.getPager());
        System.out.println("数据"+petList.getList());

过滤字段

为什么需要过滤字段

例如insert时需要忽略某个特定属性, update时只更新某些属性, 查询时需要跳过大字段等。

FieldFilter/FieldMatcher提供细致的过滤选项,包括:

  • 黑名单(locked)
  • 白名单(actived)
  • 忽略空值(ignoreNull)
  • 忽略数值0(ignoreZero)
  • 忽略日期属性(ignoreDate)
  • 忽略空字符串(ignoreBlankStr)
  • 忽略数值型主键(ignoreId), 仅insert操作有效
  • 忽略字符型主键(ignoreName), 仅insert操作有效
  • 忽略复合主键(ignorePk), 仅insert操作有效

FieldFilter是FieldMatcher的包装,相当于Map<Class, FieldMatcher>

黑名单和白名单,均为正则表达式,匹配是Java属性名!!

ignoreNull/ignoreZero/ignoreDate/ignoreBlankStr对insert(pet)无效,有多参数的insert替代.

如何过滤字段

如下代码,将只能更新 nmae 和 age

// 第二个参数是正则表达式 只能更新 nmae 和 age
        FieldFilter ff = FieldFilter.create(Pet.class, "^name|age$");
        Pet pet = dao.fetch(Pet.class, 1); //更新条件
        pet.setName("ABC"); //需要更新的字段
        pet.setAge(22);
        Daos.ext(dao, ff).update(pet);

以下代码, 将只查询id和name属性

// 第二个参数是正则表达式 只能查询 id 和 name 其他字段为类型默认值
        FieldFilter ff = FieldFilter.create(Pet.class, "^id|name$");
        Pet pet = Daos.ext(dao, ff).fetch(Pet.class, 1); //查询条件
        System.out.println(pet);

忽略空值

FieldFilter ff = FieldFilter.create(Pet.class, true);

保留几个字段且忽略空值

FieldFilter ff = FieldFilter.create(Pet.class,"^id|name|age$", true);

一对一映射

什么是一对一映射

有两张数据表,其中A表的某个字段的值指向B表的主键。因为A表的任何一条记录只能对应B表的一条且唯一一条记录,所以称这种 映射为A表对B表数据的一对一映射。(当然,反过来,你也可是说,是B表对A表的一对多映射

Pet 中就可以有一个字段 master,通过自身的 Id 指向一个 Master 对象的id,那么我们说 Pet.master 就是 Pet 对 Master 的一对一映射。

在 POJO 中配置一对一映射

在 POJO 类中字段中增加注解 @One

Pet(父表)

package com.zutz.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.nutz.dao.entity.annotation.*;

import java.io.Serializable;

@Table("t_pet")   // 声明了 pet对象的数据表
@Data
@NoArgsConstructor
public class Pet implements Serializable {

    @Id//注意,字符串主键用@Name,与属性名称无关!!!
    private int id;

    @Name    // 表示该字段可以用来标识此对象,或者是字符型主键,或者是唯一性约束(内容不能重复)
    private String name;

    @Column      // 表示该对象属性可以映射到数据库里作为一个字段
    private int age;

	//当前对象的id映射 Master对象的id
    @One(field = "masterId")
    public Master master;


}

在 Pet 对象中必须存在一个 Master 类型的字段,你的一对一映射就需要配置在这个字段上。通过 @One 注解告诉 Nutz.Dao 对象 Pet 和 Master 对象的关系,其中:

  • field 表示你打算依靠本对象的哪一个字段来映射目标对象的主键

因此:

  • POJO 类中必须存在一个属性,本 POJO 将通过该属性同目标 POJO 类的主键 关联
  • 该属性必须同目标 POJO (Master)的主键类型相同
  • 注意,这里是大小写敏感的。

补充: 在一对一映射中,有可能会存在两边对应字段名称不同的情况,所以可以通过附加 key 来说明

@Table("t_pet")
     public class Pet extends Pojo {
         @Id
         public int masterId;
		//当前对象masterId映射Master对象的id
         @One(field = "masterId", key = "id")
         public Master master;

     }

Master(子表)

package com.zutz.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;

import java.util.Date;

@Table("t_maste")
@Data
@NoArgsConstructor
public class Master {
    @Id
    private int id;
    @Column
    private String  course ;
    @Column
    private int  price;
    @Column
    private Date date;

}

创建表

dao.create(Pet.class, false);
       dao.create(Master.class, false);

插入操作

如果你已经实现准备好了这样的对象:

Pet pet = new Pet();
        pet.setName("胡汉三");
        pet.setAge(22);
        Master master = new Master();
        master.setCourse("java");
        master.setPrice(12222);
        master.setDate(new Date());
        pet.setMaster(master);

那么你可以一次将 pet 以及它对应的 master 一起插入到数据表中

dao.insertWith(pet,"master");

Nutz.Dao 会根据正则表达式 “master” 寻找可以被匹配上的映射字段(只要声明了 @One, @Many, @ManyMany 任何一个注解,都是映射字段) 并根据注解具体的配置信息,执行相应的 SQL。比如上面的操作,会实际上:

这里通过 SELECT MAX 来获取插入的最大值,是默认的做法,如果你想修改这个默认做法,请参看 关于主键一章。

  • 这里因为是一对一映射,所以会首先插入映射对象,以便用新的主键值更新主对象的映射字段
  • 如果你的对象中包括多个 @One 字段,被你的正则式匹配上,那么这些字段对应的字段(如果不为null)都会被匹配,并首先被插入

当然,你要想选择仅仅只插入映射字段的话,你可以:

dao.insertLinks(pet,"master");

并不会插入 pet 对象。

获取操作

获取父表Pet数据

Pet pet = dao.fetch(Pet.class, "胡汉三");
        System.out.println(pet);

仅获取子表master数据:

Pet pet = new Pet();
        pet.setId(1);
        Pet master = dao.fetchLinks(pet, "master");
        System.out.println(master);

同时获取父表和子表的数据

Pet pet = dao.fetchLinks(dao.fetch(Pet.class, "胡汉三"), "master");
        System.out.println(pet);

然后,你可以通过 pet.getMaster() 得到 Nutz.Dao 为 pet.master 字段设置的值。

更新操作

同时更新 pet 和 master

Pet pet = new Pet();
        pet.setId(1); //作为更新条件
        pet.setName("胡汉三1");
        pet.setAge(22);
        Master master = new Master();
        master.setId( pet.getId()); //作为更新条件
        master.setCourse("java1");
        master.setPrice(12222);
        master.setDate(new Date());
        pet.setMaster(master);

        dao.updateWith(pet, "master");

仅仅更新pet

Pet pet = new Pet();
        pet.setId(1);  //作为更新条件
        pet.setName("胡汉三");
        pet.setAge(22);

        dao.updateWith(pet, "master");

仅仅更新 master

Pet pet = new Pet();
        pet.setId(1); //作为更新条件
        Master master = new Master();
        master.setId( pet.getId()); //作为更新条件
        master.setCourse("java");
        master.setPrice(12222);
        master.setDate(new Date());
        pet.setMaster(master);
        dao.updateLinks(pet, "master");

删除操作

同时删除 pet 和 master (注意:必须使用主外键的关联列作为条件)

Pet pet = new Pet();
        pet.setId(1);
        Master master = new Master();
        master.setId(pet.getId());
        pet.setMaster(master);
        dao.deleteWith(pet, "master");

仅仅删除 pet

Pet pet = new Pet();
        pet.setId(1);;
        dao.deleteWith(pet, "master");

仅仅删除 master

Pet pet = new Pet();
        pet.setId(1);
        Master master = new Master();
        master.setId(pet.getId());
        pet.setMaster(master);
        dao.deleteLinks(pet, "master");

一对多映射

什么是一对多映射

有两张数据表,其中A表主键指向B表多条数据。所以称这种 映射为B表对A表数据的一对多映射。

简单来说就是:小明买冰箱了还买了电脑

在 POJO 中配置一对多映射

在 POJO 类中字段中增加注解 @Many

Pet (父表)

package com.zutz.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.nutz.dao.entity.annotation.*;

import java.io.Serializable;
import java.util.List;

@Table("t_pet")   // 声明了 pet对象的数据表
@Data
@NoArgsConstructor
public class Pet implements Serializable {

    @Id//注意,字符串主键用@Name,与属性名称无关!!!
    private int id;

    @Name    // 表示该字段可以用来标识此对象,或者是字符型主键,或者是唯一性约束(内容不能重复)
    private String name;

    @Column      // 表示该对象属性可以映射到数据库里作为一个字段
    private int age;

    //当前对象的id映射 Master对象的id
    @Many(field = "id")//指定映射Master的id
    public List<Master> masters;


}

Master (子表)

package com.zutz.pojo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;

import java.util.Date;

@Table("t_master")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Master {
    @Id  //主键自增
    private int masterId;
    @Column  //关联外键
    private int id;
    @Column
    private String  course ;
    @Column
    private int  price;
    @Column
    private Date date;

}

注意:因为是1对多关系 所以子表关联的字段不能是唯一约束和主键

在 Pet 对象中必须存在一个 List<Master> 类型的字段,你的一对多映射就需要配置在这个字段上。通过 @Many 注解告诉 Nutz.Dao 对象 Pet 和 Master 对象的关系,其中:

  • field 表示你打算依靠目标对象的哪一个属性来映射本对象的主键

因此:

  • 目标 POJO 类 (Master)中必须存在一个属性,用来同本 POJO 类的主键关联

  • 还要注意,这里的名称是 目标 POJO 的 JAVA 字段的名称。是大小写敏感的。

  • 该字段必须同本 POJO 类的主键类型相同

需要的表

dao.create(Pet.class, false);
        dao.create(Master.class, false);

插入操作

如果你已经实现准备好了这样的对象:

Pet pet = new Pet();
        pet.setName("胡汉三");
        pet.setAge(22);
        List< Master> pets = new ArrayList< Master>() {
            {
                add( Master.builder().course("javaee").price(12313).date(new Date()).build());
                add(Master.builder().course("javase").price(12313).date(new Date()).build());
            }
        };

        pet.setMaster( pets);

那么你可以一次将 master 以及它对应的 pets 一起插入到数据表中

dao.insertWith(pet,"masters");

Nutz.Dao 会根据正则表达式 “pets” 寻找可以被匹配上的映射字段(只要声明了 @One, @Many, @ManyMany 任何一个注解,都是映射字段) 并根据注解具体的配置信息,执行相应的 SQL。

当然,你要想选择仅仅只插入映射字段的话,你可以:

Pet pet = new Pet();
        pet.setId(1); //主键id 对应子表要插入的的id
        List< Master> pets = new ArrayList< Master>() {
            {
                add( Master.builder().course("c#").price(12313).date(new Date()).build());
                add(Master.builder().course("c").price(12313).date(new Date()).build());
            }
        };

        pet.setMaster( pets);

        dao.insertLinks(pet,"masters");//只插入子表数据

获取操作

获取父表pet 数据

Pet pet = dao.fetch( Pet.class, 1);
       System.out.println(pet);

获取子表master数据

Pet pet = new Pet();
        pet.setId(1);
        Pet master = dao.fetchLinks(pet, "masters");
        System.out.println(master);

同时获取pet和master数据

Pet pet= dao.fetchLinks(dao.fetch(Pet.class, 1), "master");
        System.out.println( pet);

然后,你可以通过 master.getPets() 得到 Nutz.Dao 为 master.pets 字段设置的值。

更新操作

同时更新 pet 和 master

Pet pet = new Pet();
        pet.setId(1); //主键id 对应子表要插修改的id
        pet.setName("王五");
        pet.setAge(33);
        List< Master> pets = new ArrayList< Master>() {
            {
                add( Master.builder().id( pet.getId()).masterId(1).course("c#").price(12313).date(new Date()).build());
                add(Master.builder().id( pet.getId()).masterId(2).course("c1").price(12313).date(new Date()).build());
            }
        };
        pet.setMaster( pets);
        dao.updateWith( pet,"masters");

注意: Master必须带@Id/@Name/@Pk中的一种或多种否则没法决定到底更新哪一个

仅更新 pet

Pet pet = new Pet();
        pet.setId(1); //主键id 更新条件
        pet.setName("王五1");
        pet.setAge(33);

        dao.updateWith( pet,"masters");

仅更新 maste

Pet pet = new Pet();
        pet.setId(1); //主键id 对应子表要插修改的id
        List< Master> pets = new ArrayList< Master>() {
            {
                add( Master.builder().id( pet.getId()).masterId(1).course("c#1").price(12313).date(new Date()).build());
                add(Master.builder().id( pet.getId()).masterId(2).course("c11").price(12313).date(new Date()).build());
            }
        };
        pet.setMaster( pets);

        dao.updateLinks( pet,"masters");

删除操作

同时删除 master 和 pets

Pet pet = new Pet();
        pet.setId(1); //主键id 对应子表要插修改的id
        List< Master> pets = new ArrayList< Master>() {
            {
                add( Master.builder().id( pet.getId()).masterId(1).build());
                add(Master.builder().id( pet.getId()).masterId(2).build());
            }
        };
        pet.setMaster( pets);
        dao.deleteWith(pet,"masters");

仅仅删除 pet

Pet pet = new Pet();
        pet.setId(1); //主键id 对应子表要插修改的id
        dao.deleteWith(pet,"masters");

仅仅删除 Maste

Pet pet = new Pet();
        pet.setId(1); //主键id 对应子表要插修改的id
        List< Master> pets = new ArrayList< Master>() {
            {
                add( Master.builder().id( pet.getId()).masterId(1).build());
                add(Master.builder().id( pet.getId()).masterId(2).build());
            }
        };
        pet.setMaster( pets);
        dao.clearLinks(pet,"masters");

多对多映射

什么是多对多映射

有两张数据表,通过第三张数据表来表示关联关系,我们称之为多对多的映射 在nutz中实现的不怎么好太僵硬了,我们可以自己进行手动关联

Pet(学生表)

package com.zutz.pojo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.nutz.dao.entity.annotation.*;

import java.io.Serializable;
import java.util.List;

@Table("t_pet")   // 声明了 pet对象的数据表
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Pet implements Serializable {

    @Id//注意,字符串主键用@Name,与属性名称无关!!!
    private int id;

    @Name    // 表示该字段可以用来标识此对象,或者是字符型主键,或者是唯一性约束(内容不能重复)
    private String name;  //名称

    @Column      // 表示该对象属性可以映射到数据库里作为一个字段
    private int age; //年龄

    @Many(field = "pid")
    private List<Pet_Master> pet_masters;

}

Master(课程表)

package com.zutz.pojo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.nutz.dao.entity.annotation.*;

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

@Table("t_master")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Master {
    @Id  //主键自增
    private int id;
    @Name
    private String  course ; //课程
    @Column
    private int  price;  //价钱
    @Column
    private Date date;  //学习时间

    @Many(field = "mid")
    private List<Pet_Master> pet_masters;
}

关联表

package com.zutz.pojo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.nutz.dao.entity.annotation.*;

import java.util.Date;

@Table("t_pet_master")
@TableIndexes({@Index(fields={"pid", "mid","state","date"}, unique=false)})//添加索引 索引名称自动生成
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Pet_Master {

    @Id
    private  int id;
    @Column
    private  int pid;
    @Column
    private  int mid;

    @Column
    private Date date; //创建时间

    @Column
    private  int  state;  //状态 0未付款 1已付款

}

在测试类进行初始化数据和创建表

dao.create(Pet.class, true); //学生表
        dao.create(Master.class, true); //课程表
        dao.create(Pet_Master.class, true);  //关联表
        List< Pet> pets = new ArrayList<Pet>() {
            {
                add(  Pet.builder().name("胡汉三").age(22).build());
                add(  Pet.builder().name("小明").age(23).build());
            }
        };
        dao.fastInsert(pets) ;

        List< Master> masters = new ArrayList< Master>() {
            {
                add( Master.builder().course("java").price(12313).date(new Date()).build());
                add(Master.builder().course("c++").price(12313).date(new Date()).build());
                add(Master.builder().course("c").price(12313).date(new Date()).build());
                add(Master.builder().course("c#").price(12313).date(new Date()).build());
            }
        };
        dao.fastInsert(masters) ;

插入数据

案例1: 小明想要买 java和c/#课程

第一步: 需要获取小明的id

第二步: 需要获取对应课程的id

地三步: 将数据插入到关联表里

//需要获取小明的id
        FieldFilter ff = FieldFilter.create(Pet.class, "^id$"); //过滤字段只显示id列,加快查询速度
        Pet p = Daos.ext(dao, ff).fetch(Pet.class, "小明");

        //需要获取对应课程的id
        FieldFilter ff1 = FieldFilter.create(Master.class, "^id$"); //过滤字段只显示id列,加快查询速度
        Criteria cri = Cnd.cri(); //复杂查询
        cri.where().andIn("course", "java", "c#");
        List<Master> m = Daos.ext(dao, ff1).query(Master.class, cri);

        //将获取的的信息存入到关联表中
        List< Pet_Master> pet_masters=new ArrayList<>();
        for (Master master : m) {
            pet_masters.add(Pet_Master.builder().pid(p.getId()).mid(master.getId()).date(new Date()).state(0).build()) ;
        }
        dao.fastInsert( pet_masters) ;

查询数据

案例1: 小明想要查询自己买了什么课程

//需要获取小明的id
        FieldFilter ff = FieldFilter.create(Pet.class, "^id$"); //过滤字段只显示id列,加快查询速度
        Pet p = Daos.ext(dao, ff).fetch(Pet.class, "小明");

        //小明的id 是2 通过小明的id 找到关联表里的所有小明课程的id
        Pet pet= dao.fetchLinks(dao.fetch(Pet.class, p.getId()), "pet_masters");
        List<Pet_Master> pet_masters = pet.getPet_masters();
        //需要获取对应课程
        for (Pet_Master pet_master : pet_masters) {
            Master master = dao.fetch(Master.class, pet_master.getMid()); //查询指定课程
            System.out.println("小明的课程:"+master);

        }

修改

案例1: 小明学java到一半不想学了,换课程为c++

//小明的id 是2 通过小明的id 找到关联表里的所有小明课程的id
        Pet pet= dao.fetchLinks(dao.fetch(Pet.class, 2), "pet_masters");
        List<Pet_Master> pet_masters = pet.getPet_masters();
        String beforeUp="java";//需要修改的课程
        String afterUp="c++";//修改后的课程
        int afterUpId=0;//修改后课程Id

        //获取需要修改的课程id
        List<Master> m = dao.query(Master.class, null);
        for (Master master : m) {
            if (master.getCourse().equals(afterUp)) {
                afterUpId=master.getId(); //讲需要修改的课程id保存
            }
        }

        //需要获取对应课程进行秀修改
        for (Pet_Master pet_master : pet_masters) {
            Master master = dao.fetch(Master.class, pet_master.getMid()); //查询指定课程
            if (master.getCourse().equals(beforeUp)) { //找到需要修改的课程
                dao.update(Pet_Master.class, Chain.make("mid",afterUpId),//修改为指定课程
                        Cnd.where("pid","=",pet.getId()).and("mid","=",master.getId()));
            }
        }

删除

案例1: 小明学完了c/#课程

//小明的id 是2 通过小明的id 找到关联表里的所有小明课程的id
        Pet pet= dao.fetchLinks(dao.fetch(Pet.class, 2), "pet_masters");
        List<Pet_Master> pet_masters = pet.getPet_masters();
        String delete="c#";//需要删除课程
        int  deleteId=0;//需要删除课程的ID
        //获取需要修改的课程id
        List<Master> m = dao.query(Master.class, null);
        for (Master master : m) {
            if (master.getCourse().equals(delete)) {
                deleteId=master.getId(); //讲需要修改的课程id保存
            }
        }
        //删除小明学完的c#课程
        dao.clear(Pet_Master.class, Cnd.where("pid","=",pet.getId()).and("mid","=",deleteId));

自定义 SQL

概述

Nutz.Dao 提供了大多数简单的操作,在80%以上的情况下,你并不需要编写 SQL,因为 Nutz.Dao 会自动替你 生成可以使用的 SQL。但是,在某些特殊的情况下,尤其是考虑到效率等问题,直接写作 SQL 仍然是程序员们 的一个杀手锏,有了这个杀手锏,程序员们永远可以针对任何数据库做他们想要的任何操作。

在之前的时代,很多程序员将 SQL 代码同 Java 代码混杂在一起,即所谓的硬编码。硬编码通常是不 好的,所以很多程序员都采用了各种办法将 SQL 提炼出来存放在一个独立的文件中。其中比较著名的一个框架 就是 iBatis。这个小巧的 SQL 映射框架(Nutz.Dao 比它还小)在这个领域里干的不错。缺省的它将 SQL 存 放在 XML 文件中,现在最新的 iBatis3 也提供了JAVA注解的写法。但是我并不认为 XML 文件或者是 JAVA 注解是存放我的 SQL 语句好地方,我认为 SQL 存放的地方,应该是可以用 Eclipse 的 SQL 编辑器打开并且 能够被正确语法高亮的一种文本文件。

著名的 Hibernate 提供 HQL, 虽然语法近似于 SQL 但是它必然会有两个不可避免的缺点

  1. 对于数据库方言支持的不好
  2. 必然会增加使用者的学习曲线

因此,Nutz.Dao 的自定义 SQL 部分的解决方案是:

用户可以硬编码 SQL 语句,比如:

Sql sql = Sqls.create("DELETE FROM t_abc WHERE name='Peter'");

支持占位符的书写方式,比如:

Sql sql = Sqls.create("DELETE FROM $table WHERE name=@name");
sql.vars().set("table","t_abc"); //表数据 $
sql.params().set("name","Peter"); //条件数据 @
// 连写
sql.setVar("table","t_abc").setVar(...);
sql.setParam("name","Peter").setParam(...);
  • $table 将会被替换成 t_abc
  • @name 将会被替换成 ?,用来创建 PreparedStatement

用户可以将所有的 SQL 语句存放在一个或者多个文件中,语句的间隔可以通过注释,比如:

/* delete.data */
DELETE FROM $table WHERE name LIKE @name
/* update.data */
UPDATE $table SET name=@name WHERE id=@id

在你的 Java 代码中:

Sql sql = dao.sqls().create("delete.data");

你可以为你的 SQL 任意定制回调,后面会有详细讲解

下面我们就由 org.nutz.dao.sql.Sql 接口入手,详细讲解一下 Nutz.Dao 的自定义 SQL 解决方案

Sql 对象 – org.nutz.dao.sql.Sql

我几乎是不加思索的将 SQL 的实现封装在一个接口后面。现在想想这到也没什么坏处。接口的默认实现是 org.nutz.dao.impl.sql.NutSql。你可以直接 new 这个对象,当然,我也提供了构造 Sql 对象的 静态方法:

如何创建 Sql 对象

通过 org.nutz.dao.Sqls 类提供的静态方法 create,你可以很方便的构建你的 Sql 对象

Sql sql = Sqls.create("INSERT INTO t_abc (name,age) VALUES('Peter',18)");

Sqls 提供的

  • fetchEntity
  • fetchInt
  • fetchString
  • fetchRecord
  • queryEntity
  • queryRecord
  • 其他等你发现

方法来帮助你构建 Sql 对象。它们之间的区别在稍后会详细说明。

通常的情况,你需要构建某些 动态 的 SQL,所以我也允许你为你的 SQL 设置占位符,占位符分两种:

变量(var)占位符 - 形式为

语法: $名称

以字符 $ 开头,名称为英文字母,数字,下划线,减号和句点。
*
正则表达式为:

[$][a-zA-Z0-9_-]

在执行 SQL 前,该占位符会被用户设置的值替换
*
类似 C 语言中的
*
参数(param)占位符 - 形式为

语法: @名称

以字符 @ 开头,名称为英文字母,数字,下划线,减号和句点。
*
在执行 SQL 前,该占位符会被字符 “?” 替换,用来创建 PreparedStatement
*
Nutz.Dao 会自动计算 PreparedStatement的索引值

所有的占位符可以同样的名称出现的多个地方。并且变量占位符和参数占位符的名称不互相干扰,比如:

Sql sql = Sqls.create("INSERT INTO $table ($name,$age,$weight) VALUES(@name,@age,@weight)");
// 为变量占位符设值
sql.vars().set("table","t_person");    
sql.vars().set("name","f_name").set("age","f_age").set("weight","f_weight");
// 为参数占位符设值
sql.params().set("name","Peter").set("age",18).set("weight",60);

通过上例,我们可以看出,变量占位符和参数占位符的确可以重名且不相互干扰的。

Sql 的逃逸字符

有些时候,有的朋友给出的 SQL 包括特殊字符 ‘@’ 或者 ‘$’,比如:

Sql sql = Sqls.create("INSERT INTO t_usr (name,email) VALUES('XiaoMing','xiaoming@163.com');"

这个时候,因为有关键字 ‘@’,所以 SQL 不能被正确解析,因为你的本意是给一个 ‘xiaoming@163.com’ 这个字符串。但是 Nutz.Dao 却认为这个是个语句参数。

这时候你可以使用逃逸字符:

Sql sql = Sqls.create("INSERT INTO t_usr (name,email) VALUES('XiaoMing','xiaoming@@163.com');"

  • 输入 “@@” 表示一个 ‘@’
  • 输入 "$" 表 示 一 个 ′ " 表示一个 '"表示一个′’

如何执行 Sql 对象

当你顺利的创建了一个 Sql 对象,执行它就相当简单了,比如:

增删改

Sql sql = Sqls.create("INSERT INTO $table (name,age) VALUES( @name,@age)");
        sql.vars().set("table","t_pet");
        sql.params().set("name", "王八").set("age",33);
        dao.execute(sql); //执行sql

查询

Sql sql = Sqls.create("SELECT name FROM t_pet WHERE name LIKE @name");
        sql.params().set("name", "胡");
        dao.execute(sql);

这就完了吗?我怎么取得查询的结果呢。是的,同 UPDATE, DELETE, INSERT 不同, SELECT 是需要返回 结果的,但是 Nutz.Dao 也不太清楚怎样为你自定义的 SELECT 语句返回结果,于是,就需要你设置回调

Tips:

如果运行的 sql 语句是类似 “show columns from tableName from dbName” 这样的语句, 请在调用 dao.execute 方法前调用 sql.forceExecQuery 方法来强制让 nutz 用 select 方式运行该 sql 语句。

回调的用处

接上例,你需要这么改造一下你的函数:

Sql sql = Sqls.create("SELECT name FROM t_pet WHERE name LIKE @name");
        sql.params().set("name", "%胡%");
        sql.setCallback(new SqlCallback() {
            public Object invoke(Connection conn, ResultSet rs, Sql sql) throws SQLException {
                List<String> list = new LinkedList<String>();
                while (rs.next())
                    list.add(rs.getString("name"));
                return list;
            }
        });
        dao.execute(sql);
        List<String> list = sql.getList(String.class);
        System.out.println(list);

看到熟悉的 ResultSet 了吧。 当然,如果你执行的不是 SELECT 语句,你依然可以设置回调,但是 ResultSet 参数就是 null了。

这种方式一般用于JOIN 查询的时候 实体类属性对应不上需要我们手动去创建一个关联的实体类然后将查询出来的数据,给指定的属性进行赋值

下代码是某个公司项目的实战代码

sql

实体类

查询代码

总结一下:

回调对象实现接口 org.nutz.dao.sql.SqlCallback,事实上,就像上例所示,这种场景非常适合使用自定义类。
1.
你的回调函数的返回值会存放在 Sql 对象中
1.
调用 sql.getResult() 可以直接返回这个对象
1.
sql.getList()(多个) 以及 sql.getObject()(单个) 方法会泛型安全的替你转型

  • 如果你的对象类型符合要求,则直接返回,否则会通过 Nutz.Castors 替你转换。
  • 对于 getList(),泛型参数用来描述集合内部元素的类型

sql.getInt() 会安全的替你将结果转成 int,如果它可以被转成 int 的话,

以下是我能想到的列表都有上面sql.getInt()类似的方法:

  • 字符串
  • 各种数字类型
  • 字符
  • 布尔类型

常用回调

Sqls.callback.XXX 提供了80%以上场景所需要的回调类型,在编写自定义回调之前,建议您先看看有没有现成的

名称结果类型备注
boolBoolean
boolsboolean[]1.r.62及之前的版本是LinkedArray
doubleValueDouble
entitiesList<Pojo>取决于Entity对应的Pojo类
entityPojo取决于Entity对应的Pojo类
floatValueFloat
integerInteger
intsint[]
longValueLong
longslong[]
mapNutMap与Record类型,但区分大小写
mapsList<NutMap>
recordRecord字段名均为小写
recordsList<Record>
strString
strListList<String>
strsString[]
timestampTimeStamp

获取返回多个对象

为了更加详细的说明一下回调的用处,我们再下面这个例子:

Sql sql = Sqls.create("SELECT * FROM t_pet");
        sql.setCallback(Sqls.callback.entities()); //查询多个对象
        sql.setEntity(dao.getEntity(Pet.class));//设置存储类
        dao.execute(sql);
        List<Pet> list = sql.getList(Pet.class);//获取查询的结果集
        System.out.println(list);

只要你保证你的 Pet类声明了 @Table 并且每个字段上的 @Column 可以同你的 ResultSet 配置起来 那么,上面的代码可以很方便的帮你获取一个 List<Pet>.

返回单个对象

Sql sql = Sqls.create("SELECT * FROM t_pet where id=1");
        sql.setCallback(Sqls.callback.entity()); //查询单个对象
        sql.setEntity(dao.getEntity(Pet.class));//设置存储类
        dao.execute(sql);
        Pet object = sql.getObject(Pet.class);
        System.out.println(object);

返回单列double

Sql sql = .create("SELECT age FROM t_pet where id=1");
        //查询结果集必须满足: 1.单列 2.小数或者整数
        sql.setCallback(Sqls.callback.doubleValue());
        dao.execute(sql);
        double aDouble = sql.getDouble(); //获取查询出来的结果
        System.out.println(aDouble);

返回单列数据集

Sql sql = Sqls.create("SELECT age FROM t_pet ");
        //查询结果集必须满足: 1.单列 2.小数或者整数
        sql.setCallback(Sqls.callback.strs());
        dao.execute(sql);
        List<String> list = sql.getList(String.class);//获取查询出来的结果
        System.out.println(list);

Nutz.Dao SQL 文件的格式

我们了解了如何构建 Sql 对象,但是一个应用通常由很多 SQL 语句构成,如何管理这些语句呢?前面我说过,我希望: " 用户可以将所有的 SQL 语句存放在一个或者多个文件中,语句的间隔可以通过注释 "。 是的这是一种非常简单的纯文本文件,文件里只包含三种信息:

  1. SQL 语句
  2. SQL 语句的名称 (或者说是键值)。你的程序可以通过语句的名称获取到某一条或几条 SQL 语句
  3. 注释 (通常包括在 // 之间)

请注意: 你的 SQL 文件必须为 “UTF-8” 编码。

下面是一个例子

/*
这里是这个 SQL 文件的注释,你随便怎么写
*/
/* sql1 */
DROP TABLE t_abc
/* 你可以随便写任何的注释文字,只有距离 SQL 语句最近的那一行注释,才会被认为是键值 */
/* getpet*/
SELECT * FROM t_pet WHERE id=@id
/* listpets*/
SELECT * FROM t_pet $condition

加载 SQL 文件

如何使用上述的 SQL 文件呢,可以将数个 SQL 文件加载到 Dao 对象中。在之后,只要得到 Dao 的对象,可以使用 dao.sqls() 方法获得 org.nutz.dao.SqlManager 接口,从这个接口中你就可以获得你预先定义好的 Sql 对象了。

需要在yaml配置文件中指定sql文件位置

dao:
    sqlmanager:
      paths:
        - sqls  #sql文件存放位置  resources/sqls

这样在Dao对象中就能直接找到sql文件

使用sql文件

获取全部sql文件的sql个数

System.out.println("sql语句的数量"+dao.sqls().count());

在resources/sql下创建selest.sql文件内容如下

/* 查询 t_pet表数据 */
/*nutz.by.get*/
SELECT * FROM $table WHERE id=@id

调用这个文件的sql

@Resource
    private Dao dao; //必须使用注入的dao才能调用sql文件
    @Test
    public void show() {
        Sql sql = dao.sqls().create("nutz.by.get");
        //查询结果集必须满足: 1.单列 2.小数或者整数
        sql.setCallback(Sqls.callback.entity());
        sql.setVar("table","t_pet"); //设置表名
        sql.setParam("id",1); //设置条件值
        sql.setEntity(dao.getEntity(Pet.class));//设置存储返回值得类
        dao.execute(sql);
        Pet object = sql.getObject(Pet.class);//获取查询出来的结果
        System.out.println(object);

    }

其他回调函数在上面有用法展示

条件占位符 – $condition

唯一需要说明的是,在你写作的 SQL 中,需要声明一个条件的占位符,比如下面的代码输出所有 id 大 于 35 的 Pet 对象的名称

Sql sql = Sqls.create("SELECT name FROM t_pet $condition");
sql.setCondition(Cnd.where("id", ">", 35)).setCallback(new SqlCallback() {
    public Object invoke(Connection conn, ResultSet rs, Sql sql) throws SQLException {
        List<String> list = new LinkedList<String>();
        while (rs.next())
            list.add(rs.getString("name"));
        return list;
    }
});
dao.execute(sql);
for (String name : sql.getList(String.class))
    System.out.println(name);

请主要看看这两行代码:

Sql sql = Sqls.create("SELECT name FROM t_pet $condition");
sql.setCondition(Cnd.where("id", ">", 35));

第一行的占位符 $condition 已经被 Nutz.Dao 保留。声明了该占位符的 SQL 都可以使用 setCondition 函数。 否则,你设置的条件将无效。

另外一个例子 - 将所有的 id 大于 35 的 Pet 对象的 masterId 设置为 45

void demoCondition2(Dao dao){
    Sql sql = Sqls.create("UPDATE t_pet SET masterid=@masterId $condition");
    sql.params().set("masterId", 45);
    sql.setCondition(Cnd.wrap("id>35"));
    dao.execute(sql);
}

多条件占位符

任意变量的值类型为Condition也就是(Cnd.where)时,均自动识别为条件占位符

Sql sql = Sqls.create("select * from user where id in (select id from vips $vip_cnd ) and name in (select name from girls $girl_cnd )");
    sql.setVar("vip_cnd", Cnd.where("level", ">", 5));
    sql.setVar("girl_cnd", Cnd.where("age", "<", 30));
    sql.setCallback(Sqls.callback.records());
    dao.execute(sql);

注意事项: 如果调用过setEntity,那么对应的Cnd会进行属性名-字段名映射.

简单分页

对于不太复杂的sql, 分页还是需要的.

Sql sql = Sqls.queryEntity("SELECT * FROM t_pet");
        // 第一个参数第几页 地二个参数每页多少行
        sql.setPager(dao.createPager(1,2));
        sql.setEntity(dao.getEntity(Pet.class));
        dao.execute(sql);
        List<Pet> list = sql.getList(Pet.class);
        System.out.println(list);

提醒一下, 用于分页的sql对象,不可重复使用!!

若自动分页失败(例如生成的sql错误),那只能自行分页了.在最上面有演示基本封装好了

Nutz填坑

明明版本是1.r.66 但是一对多的时候死活关联不上去 查询一直报错

解决办法: 使用1.r.59之前的映射方式 添加target 配置映射的实体.Class

@Many(target=BusinessFIle.class,field="businessId")
private List<BusinessFile> businessFIles;

明明在实体类中添加了对应的映射字段 ,但是查询结果就是不显示

比如:

@Column("p_consignee")
private String  consignee;

因为nutz自动会跳过为空值(Null)的属性 返回结果里不包含此属性
点赞 -收藏-关注-便于以后复习和收到最新内容有其他问题在评论区讨论-或者私信我-收到会在第一时间回复如有侵权,请私信联系我感谢,配合,希望我的努力对你有帮助^_^

相关文章