Solon v3.0.3

solon-data-sqlink [预览]

</> markdown

此插件,由社区成员(Kiryu)贡献

<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-data-sqlink</artifactId>
</dependency>

1、描述

基于Lambda表达式树的orm框架,v3.0.3版本开始支持。

2、配置示例

配置数据源(具体参考:《数据源的配置与构建》

solon.dataSources:
  db1!:
    class: "com.zaxxer.hikari.HikariDataSource"
    jdbcUrl: jdbc:mysql://localhost:3306/rock?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true
    driverClassName: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
  db2:
    class: "com.zaxxer.hikari.HikariDataSource"
    jdbcUrl: jdbc:mysql://localhost:3306/rock?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true
    driverClassName: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456

配置maven

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${maven-compiler.version}</version>
            <configuration>
                <!--必须要配置,否则不生效-->
                <compilerArgs>
                    <arg>-Xplugin:ExpressionTree</arg>
                </compilerArgs>
                <annotationProcessorPaths>
                    <!--必须要配置,否则会有意外情况-->
                    <path>
                        <groupId>org.noear</groupId>
                        <artifactId>solon-data-sqlink</artifactId>
                    </path>
                    <!-- lombok -->
                    <!--<path>-->
                    <!--    <groupId>org.projectlombok</groupId>-->
                    <!--    <artifactId>lombok</artifactId>-->
                    <!--    <version>${lombok.version}</version>-->
                    <!--</path>-->
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

配置sqlink

solon.data.sqlink:
  db1:
    # 打印sql
    printSql: true
    # 打印批量sql
    printBatch: false
    # 是否允许无where更新
    ignoreUpdateNoWhere: false
    # 是否允许无where删除
    ignoreDeleteNoWhere: false
  db2:
  # 无配置时以上为默认选项

配置数据源后,可按数据源名直接注入

import org.noear.solon.data.sqlink.SqLink;

@Component
public class DemoService {
    @Inject //默认数据源
    SqLink sqLink1;

    @Inject("db2") //db2 数据源
    SqLink sqLink2;
}

2.1 注解说明

@Table:数据库表注解

特性参数参数类型默认值功能
value表名String数据库表名
schema模式名String选择的模式

@IgnoreColumn:用于表示字段与表无关的注解(当你想要忽略某个字段)

@Column:列注解

特性参数参数类型默认值功能
value列名String代表列名
primaryKey是否为主键boolfalse是否是主键
notNull是否非nullboolfalse是否NOTNULL

@Navigate:用于表示关联关系的注解

字段参数类型默认值说明
value关联关系枚举RelationType用于表示当前类与目标类的关联关系,有四种关系(一对一,一对多,多对一,多对多)
self选择的自身字段的名称String自身类的关联关系的java字段名
target选择的目标字段的名称String目标类的关联关系的java字段名
mappingTable中间表的类对象Class<? extends IMappingTable>IMappingTable.class多对多下必填,中间表,需要继承IMappingTable
selfMapping选择的自身对应中间表的字段的名称String多对多下必填,自身类对应的mappingTable表java字段名
targetMapping选择的目标对应中间表的字段的名称String多对多下必填,目标类对应的mappingTable表java字段名

@UseTypeHandler:用于表示对字段使用指定的类型处理器

特性参数参数类型默认值功能
value实现了ITypeHandler的类对象Class<? extends ITypeHandler>指定对该字段使用的类型处理器

@InsertDefaultValue:插入数据库时字段的默认值

特性参数参数类型默认值功能
strategy生成默认值的策略GenerateStrategy指定生成默认值的来源,可以是来着数据库或静态值或动态值
value静态值String策略指定为静态值时使用
dynamic动态值Class<? extends DynamicGenerator>策略指定为动态值时使用

@OnPut:列字段相关的拦截注解

特性参数参数类型默认值功能
value拦截器类对象Class<? extends Interceptor>列字段相关的值进入数据库前触发拦截器的调用

@OnGet:列字段相关的拦截注解

特性参数参数类型默认值功能
value拦截器类对象Class<? extends Interceptor>列字段相关的值从数据库取出后触发拦截器的调用

2.2 自定义类型处理器

需要继承ITypeHandler<T>并且完全实现泛型

定义一个用来处理List的类型处理器

import org.noear.solon.annotation.Component;
import org.noear.solon.data.sqlink.base.toBean.handler.ITypeHandler;

import java.lang.reflect.Type;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

// 加上@Component注解可以注册到容器里,这意味着全局的同类型字段都会调用这个类型处理器
// 如果是用来给UseTypeHandler的话就不需要注册
@Component
public class ListStringTypeHandler implements ITypeHandler<List<String>> {

    // 实现从ResultSet取出数据的逻辑
    @Override
    public List<String> getValue(ResultSet resultSet, int index, Type type) throws SQLException {
        String string = resultSet.getString(index);
        return Arrays.stream(string.split(",")).collect(Collectors.toList());
    }

    // 实现将数据填充到PreparedStatement的逻辑
    @Override
    public void setValue(PreparedStatement preparedStatement, int index, List<String> strings) throws SQLException {
        preparedStatement.setString(index, String.join(",", strings));
    }

    // 实现将@InsertDefaultValue注解下的字符串值转换到实际的值的逻辑
    @Override
    public List<String> castStringToTarget(String value) {
        return Arrays.stream(value.split(",")).collect(Collectors.toList());
    }
}

注意:支持替换框架内默认的类型处理(int,String...)

2.3 自定义拦截器

需要继承Interceptor<T>并且完全实现泛型

定义一个需要给密码字段加密的拦截器

import org.noear.solon.data.sqlink.base.SqLinkConfig;
import org.noear.solon.data.sqlink.base.intercept.Interceptor;

public class Encryption extends Interceptor<String> {
    @Override
    public String doIntercept(String value, SqLinkConfig config) {
        return encrypt(value);
    }

    private String encrypt(String password) {
        // 加密逻辑
    }
}

再定义一个解密的拦截器

import org.noear.solon.data.sqlink.base.SqLinkConfig;
import org.noear.solon.data.sqlink.base.intercept.Interceptor;

public class Decryption extends Interceptor<String> {
    @Override
    public String doIntercept(String value, SqLinkConfig config) {
        return decrypt(value);
    }

    private String decrypt(String value) {
        // 解密逻辑
    }
}

使用实例

import demo.sqlink.interceptor.Encryption;
import demo.sqlink.interceptor.Decryption;

@Table("user")
public class User {
    private long id;
    private String username;
    @OnPut(Encryption.class)
    @OnGet(Decryption.class)
    private String password;
}

2.4 默认值注解的使用

@InsertDefaultValue注解可以在插入数据库时为没有值的字段提供默认值,有三种策列

  • 数据库提供
  • 字符串值
  • 动态生成器获取值

选择数据库提供时会直接忽略这个字段(常用于自增主键)

选择动态生成器需要继承DynamicGenerator<T>并且完全实现泛型

定义一个uuid生成器

package org.noear.solon.data.sqlink.base.generate;

import org.noear.solon.data.sqlink.base.SqLinkConfig;
import org.noear.solon.data.sqlink.base.metaData.FieldMetaData;

import java.util.UUID;

public class UUIDGenerator extends DynamicGenerator<String> {
    @Override
    public String generate(SqLinkConfig config, FieldMetaData fieldMetaData) {
        return UUID.randomUUID().toString();
    }
}

使用实例

import org.noear.solon.data.sqlink.annotation.GenerateStrategy;
import org.noear.solon.data.sqlink.annotation.InsertDefaultValue;import org.noear.solon.data.sqlink.base.generate.UUIDGenerator;

@Table("user")
public class User {
    // 数据库,假设这里是自增
    @InsertDefaultValue(strategy = GenerateStrategy.DataBase)
    private long id;
    // 静态值
    @InsertDefaultValue(strategy = GenerateStrategy.Static,value = "新用户")
    private String username;
    // 动态值
    @InsertDefaultValue(strategy = GenerateStrategy.Dynamic,dynamic = UUIDGenerator.class)
    private String uuid;
}

3、增删查改操作

实体类

import org.noear.solon.data.sqlink.annotation.*;

@Table("user")
public class User {
    // 主键
    @Column(primaryKey = true, notNull = true)
    // 数据库提供,这里是自增
    @InsertDefaultValue(strategy = GenerateStrategy.DataBase)
    private long id;
    private String username;
    private String password;
    private String email;
}
  • 插入数据
import demo.sqlink.model.User;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.data.sqlink.SqLink;

import java.util.Arrays;

@Component
public class InsertDemoService {
    @Inject 
    SqLink sqLink;

    // 插入一条
    public long insert(String username, String password) {
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        return sqLink.insert(user).executeRows();
    }

    // 插入多条,自动启用批量
    public long batchInsert() {
        User user1 = new User();
        user1.setUsername("solon");
        user1.setPassword("aaa");
        User user2 = new User();
        user2.setUsername("noear");
        user2.setPassword("bbb");
        User user3 = new User();
        user3.setUsername("没有耳朵");
        user3.setPassword("ccc");
        return sqLink.insert(Arrays.asList(user1, user2, user3)).executeRows();
        // or sqLink.insert(user1).insert(user2).insert(user3).executeRows();
    }
}
  • 更新数据
import demo.sqlink.model.User;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.data.sqlink.SqLink;

@Component
public class UpdateDemoService {
    @Inject
    SqLink sqLink;

    // 根据id更新email
    public void updateEmailById(int id, String newEmail) {
        // UPDATE user SET email = {newEmail} WHERE id = {id}
        sqLink.update(User.class)
                .set(u -> u.setEmail(newEmail))
                .where(u -> u.getId() == id)
                .executeRows();
    }

    // 根据id更新name和email
    public void updateNameAndEmailById(int id, String newName, String newEmail) {
        // UPDATE user SET email = {newEmail}, username = {newName} WHERE id = {id}
        sqLink.update(User.class)
                .set(u -> {
                    u.setEmail(newEmail);
                    u.setUsername(newName);
                })
                .where(u -> u.getId() == id)
                .executeRows();
    }
}
  • 删除数据
import demo.sqlink.model.User;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.data.sqlink.SqLink;

@Component
public class DeleteDemoService {
    @Inject
    SqLink sqLink;

    // 根据id删除
    public long deleteById(int id) {
        return sqLink.delete(User.class)
                .where(u -> u.getId() == id)
                .executeRows();
    }

    // 根据id和name删除
    public long deleteByName(int id, String name) {
        return sqLink.delete(User.class)
                .where(u -> u.getId() == id && u.getUsername() == name)
                .executeRows();
    }

    // 删除所有错误的邮箱:(
    public long deleteByBadEmail() {
        return sqLink.delete(User.class)
                // NOT (email LIKE CONCAT('%','@','%'))
                .where(u -> !u.getEmail().contains("@"))
                .executeRows();
    }
}
  • 查询数据
import demo.sqlink.model.User;
import demo.sqlink.vo.UserVo;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.data.sqlink.SqLink;
import org.noear.solon.data.sqlink.api.Result;
import org.noear.solon.data.sqlink.core.sqlExt.SqlFunctions;

import java.util.List;

@Component
public class SelectDemoService {
    @Inject
    SqLink sqLink;

    // 根据id查询一位用户
    public User findById(long id) {
        return sqLink.query(User.class)
                .where(user -> user.getId() == id)
                .first();
    }

    // 根据名称和email查询一位用户
    public User findByNameAndEmail(String name, String email) {
        return sqLink.query(User.class)
                .where(u -> u.getUsername() == name && u.getEmail() == email)
                .first();
    }

    // 根据名称查询模糊匹配用户
    public List<User> findByName(String name) {
        return sqLink.query(User.class)
                // username LIKE '{name}%'
                .where(u -> u.getUsername().startsWith(name))
                .toList();
    }

    // 根据名称查询模糊匹配用户, 并且以匿名对象形式返回我们感兴趣的数据
    public List<? extends Result> findResultByName(String name) {
        return sqLink.query(User.class)
                // username LIKE '{name}%'
                .where(u -> u.getUsername().startsWith(name))
                .select(u -> new Result() {
                    long id = u.getId();
                    String email = u.getEmail();
                }).toList();
    }

    // 或者使用Vo返回
    public List<UserVo> findUserVoByName(String name) {
        return sqLink.query(User.class)
                .where(u -> u.getUsername().startsWith(name))
                .select(UserVo.class).toList();
    }
}