sharp-database 添加EntityDAOSupport支持,省略 DAO 模版代码

很多时候DAO继承父类都可以满足操作需求, 所以大部分情况下继承 EntityDAOImpl 变成一个样板代码, sharp-database 做了增强,可以不显示地继承 EntityDAOImpl, 可以通过扫描实体类自动完成. 如果自己继承父类,那么就会忽略扫描.

// 这类可以有扫描实体类自动完成
@Repository
public class BomDAO extends EntityDAOImpl<Bom, Long> {
    // 空
}

实例

添加对 entity 自动扫描的支持

启动类添加注解 @DependsOn("entityDAOSupport")

@SpringBootApplication
@EnableCaching
@DependsOn("entityDAOSupport")
public class SharpAdminApplication {

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

}

实体类

编写实体类 Bom.java Item.java
Bom 1 <=> N Item

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@SuperBuilder
@Table(value = "produce_bom", comment = "物料BOM表")
public class Bom extends BaseEntity {

    @Column(comment = "物料")
    Long materialId;

    @OneToMany(subTable = "produce_bom_detail", reversePropertyName = "bomId", cascadeInsertOrUpdate = true, joinValue = "bom_id")
    List<Item> itemList;

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @FieldDefaults(level = AccessLevel.PRIVATE)
    @SuperBuilder
    @Table(value = "produce_bom_detail", comment = "物料BOM表详情")
    public static class Item extends BaseEntity {

        @Column(comment = "物料")
        Long materialId;

        BigDecimal quantity;

        String unit;

        String remark;

        Long bomId;
    }

}

实体类生成Table

    tableGenerator.createTable(Bom.class);
    tableGenerator.createTable(Bom.Item.class);

配置实体扫描

application.yml

sharp:
  database:
    entity-base-package: com.rick.admin.module.**.entity

测试

1. EntityDAOSupport

    @Autowired
    private EntityDAOSupport entityDAOSupport

    @Test
    public void testInsertBom() {
        Bom bom = Bom.builder()
                .materialId(697175396411510784L)
                .itemList(Arrays.asList(
                        Bom.Item.builder()
                                .materialId(697175443165417472L)
                                .quantity(BigDecimal.TEN)
                                .unit("KG")
                                .remark("remark")
                                .build()
                ))
                .build();

        // 不参与包扫描也可以使用
        entityDAOSupport.getEntityDAO(bom.getClass()).insert(bom);
    }

2. 包扫描自动注入

bomDAO 是DAO bean的名字. 规则是「实体类名驼峰+DAO」,如果是内部类「外部类+内部实体类名驼峰+DAO」,比如例子中的 bomItemDAO

    /**
     * entity 参与包扫描,可以注入使用
     */
    @Autowired(required = false)
    @Qualifier("bomDAO")
    private EntityDAO<Bom, Long> entityDAO;

    @Test
    public void testInsertBom() {
        Bom bom = Bom.builder()
                .materialId(697175396411510784L)
                .itemList(Arrays.asList(
                        Bom.Item.builder()
                                .materialId(697175443165417472L)
                                .quantity(BigDecimal.TEN)
                                .unit("KG")
                                .remark("remark")
                                .build()
                ))
                .build();

        entityDAO.insert(bom);
    }

3. 手动获取DAO bean

    @Resource
    private ApplicationContext applicationContext;

    @Test
    public void testInsertBom() {
        Bom bom = Bom.builder()
                .materialId(697175396411510784L)
                .itemList(Arrays.asList(
                        Bom.Item.builder()
                                .materialId(697175443165417472L)
                                .quantity(BigDecimal.TEN)
                                .unit("KG")
                                .remark("remark")
                                .build()
                ))
                .build();

        applicationContext.getBean("bomDAO", EntityDAO.class)
                .insert(bom);
    }

04.属性名和bean名保持一致,spring按照名称自动注入(推荐)

    // @Autowired
    @Resource
    private EntityDAO<Bom, Long> bomDAO;

    @Test
    public void testInsertBom() {
        Bom bom = Bom.builder()
                .materialId(697175396411510784L)
                .itemList(Arrays.asList(
                        Bom.Item.builder()
                                .materialId(697175443165417472L)
                                .quantity(BigDecimal.TEN)
                                .unit("KG")
                                .remark("remark")
                                .build()
                ))
                .build();

        bomDAO.insert(bom);
    }

注意事项

如果没有手动编写 DA0 类,那么根据 BeanName 进行注册, 写法是固定的.

@Resource
private EntityDAO<Entity, Long> entityDAO;

如果手动编写 DA0 类,那么根据 BeanTypeBeanName 进行注册, 可以使用两种方式进行注册. 都是同一引用. 比如:

    @Resource
    private EntityDAO<Dog, Long> dogDAO2; // 指定了范型, 根据类型注册名字随便起 

    // @Resource
    // private EntityDAO dogDAO3; // 如果写成这样, 启动会抛出异常找不到合适的Bean

    // @Resource
    // private EntityDAO dogDAO; // 如果写成这样没有使用范型, 根据Bean的名称注册, 使用的时候需要强转, 不推荐使用此方式. 

    @Resource
    private DogDAO dogDAO;

    @Test
    public void test() {
        Assertions.assertThat(dogDAO2 == dogDAO).isEqualTo(true);
    }

监听端口,未监控到则后台启动

schedule.bat

@echo off

netstat -aon|findstr "7000" >nul 2>nul && set state=1 || set state=0
if %state%==1 (echo "Running...")
if %state%==0 (
D:
cd D:\AC\nginx-1.20.2
nginx -s stop
start nginx
cd ..
start jdk1.8.0_251\bin\javaw -jar ac-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod,dict
)

exit

schedule.bat 可以加入计划任务

sharp-database 通过SQL多表查询, 并组装返回Bean

业务Bean SchoolBO.java

@Data
public class SchoolBO {

    private Long id;

    private String name;

    private LocalDate buildDate;

    private SchoolLicenseBO sl;

    private List<StudentBO> studentList;

    private List<TeacherBO> teacherList;

    /**
     * 1 <-> 1
     */
    @Data
    public static class SchoolLicenseBO {

        private Long id;

        private String number;

        private String remark;
    }

    /**
     * 1 <-> N
     */
    @Data
    public static class StudentBO {

        private Long id;

        private String name;

        private Integer grade;

        private String sex;

        private Long schoolId;
    }

    /**
     * N <-> N
     */
    @Data
    public static class TeacherBO {

        private Long id;

        private String name;

        private Integer age;

        private Long schoolId;
    }
}

查询测试:

@Test
public void testQueryUtils() {
    String parentSql = "select s.id,\n" +
            "       s.name,\n" +
            "       s.build_date as \"buildDate\",\n" +
            "       sl.id as \"sl.id\",\n" +
            "       sl.number as \"sl.number\",\n" +
            "       sl.remark as \"sl.remark\"\n" +
            "from t_school s\n" +
            "         LEFT JOIN t_school_license sl on sl.id = s.school_license_id\n" +
            "where s.id IN (:id)";

    List<SchoolBO> schoolBOList = sharpService.query(parentSql, Params.builder(1).pv("id", "552173736070144000, 552181593272410112").build(), SchoolBO.class);

    if (CollectionUtils.isEmpty(schoolBOList)) {
        return;
    }

    Collection<?> refValues = schoolBOList.stream().map(SchoolBO::getId).collect(Collectors.toSet());

    // 1 <-> N
    // select id, name, grade, sex from t_school_student where school_id IN (552173736070144000, 552181593272410112);
    Map<?, List<SchoolBO.StudentBO>> schoolStudentMap = QueryUtils.subTableValueMap("id, name, grade, sex, school_id", "t_school_student", "school_id", refValues, SchoolBO.StudentBO.class);

    // N <-> N
    // select t.id, t.name, t.age, school_id from t_school_teacher_related r INNER JOIN t_school_teacher t ON r.teacher_id = t.id where school_id IN (552173736070144000, 552181593272410112)
    Map<?, List<SchoolBO.TeacherBO>> schoolTeacherMap = QueryUtils.subTableValueMap("t.id, t.name, t.age, school_id", "t_school_teacher_related r INNER JOIN t_school_teacher t ON r.teacher_id = t.id", "school_id", refValues, SchoolBO.TeacherBO.class);

    schoolBOList.forEach(t -> {
        t.setStudentList(ObjectUtils.defaultIfNull(schoolStudentMap.get(t.getId()), Collections.emptyList()));
        t.setTeacherList(ObjectUtils.defaultIfNull(schoolTeacherMap.get(t.getId()), Collections.emptyList()));
    });

    System.out.println(schoolBOList);
}

参考阅读: https://xhope.top/?p=1577

sharp-database通过注解@Version实现乐观锁

当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:

取出记录时,获取当前 version
更新时,带上这个 version
执行更新时, set version = newVersion where version = oldVersion
如果 version 不对,就更新失败(更新影响的行数为0)

Notice.java 属性添加 @Version

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@SuperBuilder
@Table(value = "t_notice", comment = "通知公告")
public class Notice extends BaseEntity {

    String name;

    @Version
    Integer version;

}

生成SQL语句

create table `sharp-demo`.t_notice
(
    id          bigint      not null comment '主键'
        primary key,
    name        varchar(32) null,
    version     int         not null comment '版本号',
    create_by   bigint      null,
    create_time datetime    null comment '创建时间',
    update_by   bigint      null,
    update_time datetime    null comment '修改时间',
    is_deleted  bit         null comment '是否逻辑删除'
)
    comment '通知公告' charset = utf8mb3;

测试

新增

    @Order(0)
    @Test
    public void testNoticeInsert() {
        noticeDAO.insert(Notice.builder()
                        .name("hello new version")
                .build());
        // SQL => [INSERT INTO t_notice(name,version,create_by,create_time,update_by,update_time,is_deleted,id) VALUES(?,?,?,?,?,?,?,?)], args:=> [[hello new version, 1, 0, 2023-05-14T03:08:00.944, 0, 2023-05-14T03:08:00.944, false, 688973584810381312]]
    }

修改

    @Order(1)
    @Test
    public void testNoticeUpdate() {
        Notice notice = noticeDAO.selectById(688436409735868416L).get();
        notice.setName("new didi");
        noticeDAO.update(notice);
        // SQL => [UPDATE t_notice SET name = ?,version = ?,update_by = ?,update_time = ?,is_deleted = ? WHERE id = ? AND version = ? AND is_deleted = ?], args:=> [[new didi, 2, 0, 2023-05-12T15:33:28, false, 688436409735868416, 1, false]]
    }

IntelliJ 创建Sharp DB模板(Entity/Enum/DAO/Service)

File Header

/**
 * @author Rick.Xu
 * @date ${DATE} ${TIME}
 */

QueryResult

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end

import com.rick.db.plugin.dao.annotation.Table;
import lombok.Getter;
import lombok.Setter;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;

#parse("File Header.java")
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
@Table
public class ${NAME} {

}

Entity

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end

import com.rick.db.dto.BaseEntity;
import com.rick.db.plugin.dao.annotation.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;

#parse("File Header.java")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@SuperBuilder
@Table(value = "table_name", comment = "表描述")
public class ${NAME} extends BaseEntity {

}

EnumCode

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end

import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.HashMap;
import java.util.Map;

#parse("File Header.java")
@AllArgsConstructor
@Getter
public enum ${NAME}Enum {
    NAME1(0, "NAME1"),
    NAME2(1, "NAME2");
    private static final Map<Integer, ${NAME}Enum> codeMap = new HashMap<>();
    static {
        for (${NAME}Enum e : values()) {
            codeMap.put(e.code, e);
        }
    }
    private final int code;
    private final String label;
    @JsonValue
    public int getCode() {
        return this.code;
    }

    /**
     * code枚举 必须重写toString()
     * @return
     */
    @Override
    public String toString() {
        return String.valueOf(code);
    }

    public static ${NAME}Enum valueOfCode(int code) {
        return codeMap.get(code);
    }
}

EnumCode2

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.HashMap;
import java.util.Map;

#parse("File Header.java")
@AllArgsConstructor
@Getter
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ${NAME}Enum {
    NAME1(0, "NAME1"),
    NAME2(1, "NAME2");
    private static final Map<Integer, ${NAME}Enum> codeMap = new HashMap<>();
    static {
        for (${NAME}Enum e : values()) {
            codeMap.put(e.code, e);
        }
    }
    private final int code;
    private final String label;

    public int getCode() {
        return this.code;
    }

    /**
     * code枚举 必须重写toString()
     * @return
     */
    @Override
    public String toString() {
        return String.valueOf(code);
    }

    public static ${NAME}Enum valueOfCode(int code) {
        return codeMap.get(code);
    }
}

EnumName

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end

import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;

#parse("File Header.java")
@AllArgsConstructor
@Getter
public enum ${NAME}Enum {
    NAME1("NAME1"),
    NAME2("NAME2");

    @JsonValue
    public String getCode() {
        return this.name();
    }
    private final String label;
    public static ${NAME}Enum valueOfCode(String code) {
        return valueOf(code);
    }
}

EnumName2

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Getter;

#parse("File Header.java")
@AllArgsConstructor
@Getter
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ${NAME}Enum {
    NAME1("NAME1"),
    NAME2("NAME2");

    public String getCode() {
        return this.name();
    }
    private final String label;
    public static ${NAME}Enum valueOfCode(String code) {
        return valueOf(code);
    }
}

DAO

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")#set($folderIndex = $PACKAGE_NAME.length() - 4)#end

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end

import com.rick.db.plugin.dao.core.EntityDAOImpl;
import #if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")${PACKAGE_NAME.substring(0, $folderIndex)}.entity.#end${NAME};
import org.springframework.stereotype.Repository;

#parse("File Header.java")
@Repository
public class ${NAME}DAO extends EntityDAOImpl<${NAME}, Long> {

}

Service

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")#set($folderIndex = $PACKAGE_NAME.length() - 8)#end
#set($length = $NAME.length())

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end

import #if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")${PACKAGE_NAME.substring(0, $folderIndex)}.dao.#end${NAME}DAO;
import lombok.RequiredArgsConstructor;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

#parse("File Header.java")
@Service
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
@RequiredArgsConstructor
@Validated
public class ${NAME}Service {
    ${NAME}DAO ${NAME.substring(0,1).toLowerCase()}${NAME.substring(1,$length)}DAO;
}