ORM框架Spring-Data-JPA基本使用

6/8/2021 Spring Data JPAMybatisDruidScrew

# 1. ORM框架技术选型

# 1.1 Spring Data JPA

JPA 是 Java Persistence API 的简称,中文名为 Java 持久层 API,是 JDK 5.0 注解或 XML 描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。可以理解为 JPA 规范的再次封装抽象,底层还是使用了 Hibernate 的 JPA 技术实现,引用 JPQL查询语言,属于 Spring 整个生态体系的一部分。

随着 Spring Boot 和 Spring Cloud 在市场上的流行,Spring Data JPA 也逐渐进入大家的视野,它们组成有机的整体,使用起来比较方便,加快了开发的效率,使开发者不需要关心和配置更多的东西,完全可以沉浸在 Spring 的完整生态标准实现下。JPA 上手简单,开发效率高,对对象的支持比较好,又有很大的灵活性,市场的认可度越来越高。

SpringDataJPA架构

# 1.2 Mybatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO为数据库中的记录。

Mybatis的功能架构分为三层:

  • API接口层:提供给外部使用的接口API,开发人员通过这些 API 操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
  • 数据处理层:负责具体的SQL查找,SQL解析,SQL执行和执行结果映射处理等。主要目的是根据调用的请求完成一次数据库操作。
  • 基础支撑层:负责最基础的功能支撑,包括连接管理,事务管理,配置加载和缓存处理,这些都是共用的,将它们抽取出来作为最基础的组件,为上层的数据处理层提供最基础的支撑。

# 2. Spring Data JPA框架

# 2.1 整合JPA框架

# 2.1.1 依赖及配置文件

pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-hibernate5</artifactId>
            <version>2.12.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

application.properties

# JPA config
spring.jpa.database=MYSQL
spring.jpa.show-sql=true
spring.jpa.open-in-view=true
spring.jpa.hibernate.ddl-auto=update
1
2
3
4
5

# 2.1.2 JPA配置说明

其中jpa.hibernate.ddl-auto是hibernate的配置属性,其主要作用是自动创建、更新、验证数据库表结构。该参数的几种配置选型如下:

  • create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
  • create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
  • update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
  • validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。

# 2.2 JPA常用注解及自定义简单查询

# 2.2.1 JPA常用注解

JPA的常用注解如下:

注解 说明
@Entity 声明类为实体。
@Table 声明表名,@Entity和@Table注解一般块使用,如果表名和实体类名相同,那么@Table可以省略。
@Basic 指定非约束明确的各个字段。
@Embedded 用于注释属性,表示该属性的类是嵌入类( @embeddable 用于注释Java类的,表示类是嵌入类)。
@ld 指定的类的属性,一个表中的主键。
@GeneratedValue 指定如何标识属性可以被初始化,如@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "repair_seq"):表示主键生成策略是sequence,还有Auto、 ldentity、 Native 等。
@Transient 表示该属性并非一个数据库表的字段的映射,ORM框架将忽略该属性。如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,即它是不持久的,为虚拟字段。
@Column 指定持久属性,即字段名。如果字段名与列名相同,则可以省略。使用方法如:@Column(length=11,name="phone",nullable=false,columnDefinition = "varchar(11) unique comment'电话号码'")
@SequenceGenerator 指定在 @SequenceGenerator 注解中指定的属性的值。它创建一个序列。
@TableGenerator 在数据库生成一张表来管理主键生成策略。
@AccessType 这种类型的注释用于设置访问类型。如果设置@AccessType(FIELD),则可以直接访问变量,并且不需要使用Gtter和Stter方法,但必须为public属性。如果设置@AccessType(PROPERTY),则通过Getter和Setter方法访问Entity的变量。
@UniqueConstraint 指定的字段和用于主要或辅助表的唯一约束。
@ColumnResult 可以参考使用select子句的SQL查询中的列名。
@NamedQueries 指定命名查询的列表。
@NamedQuery 指定使用静态名称的查询。
@Basic 指定实体属性的加载方式,如@Basic(fetch=FetchType.LAZY)。
@Jsonlgnore 作用是JSON序列化时将Java Bean中的一些属性忽略掉,序列化和反序列化都受影响。

# 2.2.2 映射关系的注解与属性

映射关系的注解:

注解 说明
@JoinColumn 指定一个实体组织或实体集合。用在“多对一”和“一对多”的关联中。
@OneToOne 定义表之间“一对一”的关系。
@OneToMany 定义表之间“一对多”的关系。
@ManyToOne 定义表之间“多对一”的关系。
@ManyToMany 定义表之间“多对多”的关系。

映射关系的属性:

属性名 说明
targetEntity 表示默认关联的实体类型,默认为当前标注的实体类。
cascade 表示与此实体一对一关联的实体的级联样式类型,以及当对实体进行操作时的策略。在定义关系时经常会涉及是否定义Cascade(级联处理)属性,如果担心级联处理容易造成负面影响,则可以不定义。它的类型包括CascadeType.PERSIST(级联新建)、CascadeType.REMOVE (级联删除)、CascadeType.REFRESH(级联刷新)、CascadeType.MERGE(级联更新)、CascadeType.ALL (级联新建、更新、删除、刷新)。
fetch 该实体的加载方式,包含 LAZY 和 EAGER。
optional 表示关联的实体是否能够存在null值。默认为true,表示可以存在null值。如果为false,则要同时配合使用@JoinColumn标记。
mappedBy 双向关联实体时使用,标注在不保存关系的实体中。
JoinColumn 关联指定列。该属性值可接收多个@JoinColumn。用于配置连接表中外键列的信息。@JoinColumn配置的外键列参照当前实体对应表的主键列。
JoinTable 两张表通过中间的关联表建立联系时使用,即多对多关系。
PrimaryKeyJoinColumn 主键关联。在关联的两个实体中直接使用注解@PrimaryKeyJoinColumn注释。

懒加载LAZY与实时加载EAGER对比:

懒加载LAZY和实时加载EAGER的目的是,实现关联数据的选择性加载。懒加载是在属性被引用时才生成查询语句,抽取相关联数据。实时加载则是执行完主查询后,不管是否被引用,都会马上执行后续的关联数据查询。使用懒加载来调用关联数据,必须要保证主查询的数据库连接会话的生命周期没有结束,否则是无法抽取到数据的。

# 2.2.3 自定义简单查询

Spring Data JPA 为此提供了一些表达条件查询的关键字,大致如下:

  • And --- 等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(String user, Striang pwd);
  • Or --- 等价于 SQL 中的 or 关键字,比如 findByUsernameOrAddress(String user, String addr);
  • Between --- 等价于 SQL 中的 between 关键字,比如 findBySalaryBetween(int max, int min);
  • LessThan --- 等价于 SQL 中的 "<",比如 findBySalaryLessThan(int max);
  • GreaterThan --- 等价于 SQL 中的">",比如 findBySalaryGreaterThan(int min);
  • IsNull --- 等价于 SQL 中的 "is null",比如 findByUsernameIsNull();
  • IsNotNull --- 等价于 SQL 中的 "is not null",比如 findByUsernameIsNotNull();
  • NotNull --- 与 IsNotNull 等价;
  • Like --- 等价于 SQL 中的 "like",比如 findByUsernameLike(String user);
  • NotLike --- 等价于 SQL 中的 "not like",比如 findByUsernameNotLike(String user);
  • OrderBy --- 等价于 SQL 中的 "order by",比如 findByUsernameOrderBySalaryAsc(String user);
  • Not --- 等价于 SQL 中的 "!=",比如 findByUsernameNot(String user);
  • In --- 等价于 SQL 中的 "in",比如 findByUsernameIn(Collection<String> userList) ,方法参数可以是 Collection 类型,也可以是数组或不定长参数;
  • NotIn --- 等价于 SQL 中的 "not in",比如 findByUsernameNotIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;

# 2.3 JPA实体基类

可以创建一个JPA实体基类,里面放置通用字段,@MappedSuperclass注解使其多个类继承后可以生成多个表(如果改成@Entity,则继承后,多个类继承,只会生成一个表),业务表可以直接继承它进行创建。

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@Data
@MappedSuperclass
@ApiModel(value = "BaseEntity", description = "实体基类(用于业务实体统一继承)")
public class BaseEntity implements Serializable {

    /**
     * 主键
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @ApiModelProperty(value = "主键")
    @Column(name = "id", columnDefinition = "bigint COMMENT '主键'")
    private Long id;

    /**
     * 创建时间
     */
    @CreationTimestamp
    @ApiModelProperty(value = "创建时间")
    @Column(name = "create_time", updatable = false, columnDefinition = "datetime COMMENT '创建时间'")
    private Date createTime;

    /**
     * 更新时间
     */
    @UpdateTimestamp
    @ApiModelProperty(value = "更新时间")
    @Column(name = "update_time", columnDefinition = "datetime COMMENT '更新时间'")
    private Date updateTime;

    /**
     * 创建人
     */
    @JsonIgnore
    @ManyToOne
    @ApiModelProperty(value = "创建人")
    @JoinColumn(name = "create_user_id", updatable = false, referencedColumnName = "id", foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT), columnDefinition = "bigint COMMENT '创建人ID'")
    private User createUser;

    /**
     * 更新人
     */
    @JsonIgnore
    @ManyToOne
    @ApiModelProperty(value = "更新人")
    @JoinColumn(name = "update_user_id", referencedColumnName = "id", foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT), columnDefinition = "bigint COMMENT '更新人ID'")
    private User updateUser;

    /**
     * 是否删除
     */
    @ApiModelProperty(value = "是否删除 0:未删除 1:已删除")
    @Column(name = "is_deleted", columnDefinition = "int COMMENT '是否删除 0:未删除 1:已删除'")
    @JsonIgnore
    private Integer isDeleted = 0;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

使用方法:

public class BusinessEntity extends BaseEntity {}
1

# 2.4 JPA实现基本增删查改

以下是JPA实现单表基本增删查改的示例代码(删减了权限控制及日志管理的注解),可作为业务开发的初始化模板。这里的实体基类表只是用来写demo进行测试,实际业务开发请使用上面的那个进行继承。

Base.java

package com.yoyo.admin.common.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

/**
 * 实体基类表
 */
@Data
@Entity
@ApiModel(value = "base", description = "实体基类表")
@Table(name = "base")
@org.hibernate.annotations.Table(appliesTo = "base", comment = "实体基类表")
public class Base implements Serializable {

    /**
     * 主键
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @ApiModelProperty(value = "主键")
    @Column(name = "id", columnDefinition = "bigint COMMENT '主键'")
    private Long id;

    /**
     * 创建时间
     */
    @CreationTimestamp
    @ApiModelProperty(value = "创建时间")
    @Column(name = "create_time", updatable = false, columnDefinition = "datetime COMMENT '创建时间'")
    private Date createTime;

    /**
     * 更新时间
     */
    @UpdateTimestamp
    @ApiModelProperty(value = "更新时间")
    @Column(name = "update_time", columnDefinition = "datetime COMMENT '更新时间'")
    private Date updateTime;

    /**
     * 创建人
     */
    @JsonIgnore
    @ManyToOne
    @ApiModelProperty(value = "创建人")
    @JoinColumn(name = "create_user_id", updatable = false, referencedColumnName = "id", foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT), columnDefinition = "bigint COMMENT '创建人ID'")
    private User createUser;

    /**
     * 更新人
     */
    @JsonIgnore
    @ManyToOne
    @ApiModelProperty(value = "更新人")
    @JoinColumn(name = "update_user_id", referencedColumnName = "id", foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT), columnDefinition = "bigint COMMENT '更新人ID'")
    private User updateUser;

    /**
     * 备注
     */
    @ApiModelProperty(value = "备注")
    @Column(name = "remark", columnDefinition = "varchar(255) COMMENT '备注'")
    private String remark;

    /**
     * 是否删除
     */
    @ApiModelProperty(value = "是否删除 0:未删除 1:已删除")
    @Column(name = "is_deleted", columnDefinition = "int COMMENT '是否删除 0:未删除 1:已删除'")
    @JsonIgnore
    private Integer isDeleted = 0;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

BaseRepository.java

package com.yoyo.admin.common.domain.repository;

import com.yoyo.admin.common.domain.Base;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

/**
 * 实体基类管理
 */
public interface BaseRepository extends JpaRepository<Base, Long> {

    /**
     * 根据id查询记录
     */
    Base findFirstById(Long id);

    /**
     * 查询所有未删除的记录
     */
    List<Base> findAllByIsDeleted(Integer isDeleted);

    /**
     * 分页查询符合条件的记录
     */
    Page<Base> findAll(Specification specification, Pageable pageable);

    /**
     * 列表查询符合条件的记录
     */
    List<Base> findAll(Specification specification, Sort sort);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

BaseDemoService.java

package com.yoyo.admin.common.service.demo;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.StrUtil;
import com.yoyo.admin.common.domain.Base;
import com.yoyo.admin.common.domain.User;
import com.yoyo.admin.common.domain.repository.BaseRepository;
import com.yoyo.admin.common.logger.annotation.CreateLog;
import com.yoyo.admin.common.logger.annotation.DeleteLog;
import com.yoyo.admin.common.logger.annotation.UpdateLog;
import com.yoyo.admin.common.service.UserService;
import com.yoyo.admin.common.utils.ObjectConvertUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class BaseDemoService {

    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    private BaseRepository baseRepository;

    @Autowired
    public void setBaseRepository(BaseRepository baseRepository) {
        this.baseRepository = baseRepository;
    }

    /**
     * 新增基础实体
     *
     * @param body
     */
    @Transactional(rollbackFor = Exception.class)
    public Base createBase(Map<String, Object> body) throws Exception {
        String remark = ObjectConvertUtils.parseString(body.get("remark"));
        if (remark == null || StrUtil.isEmpty(remark)) {
            throw new Exception("备注不可为空");
        }
        User user = userService.current();
        Base base = new Base();
        try{
            base.setRemark(remark);
            base.setCreateUser(user);
            base.setUpdateUser(user);
            baseRepository.save(base);
        }catch(Exception e){
            e.printStackTrace();
        }
        return base;
    }

    /**
     * 修改基础实体
     *
     * @param body
     */
    @Transactional(rollbackFor = Exception.class)
    public Base updateBase(Map<String, Object> body) throws Exception {
        Long id = ObjectConvertUtils.parseLong(body.get("id"));
        if (id == null) {
            throw new Exception("id不可为空");
        }
        String remark = ObjectConvertUtils.parseString(body.get("remark"));
        if (remark == null || StrUtil.isEmpty(remark)) {
            throw new Exception("备注不可为空");
        }
        User user = userService.current();
        Base base = new Base();
        base.setId(id);
        base.setRemark(remark);
        base.setUpdateUser(user);
        try{
            Base oldBase = baseRepository.findFirstById(base.getId());
            BeanUtil.copyProperties(base, oldBase, CopyOptions.create().setIgnoreNullValue(true));
            oldBase.setUpdateUser(user);
            baseRepository.save(oldBase);
        }catch(Exception e){
            e.printStackTrace();
        }
        return base;
    }

    /**
     * 批量删除基础实体
     *
     * @param idList
     */
    @Transactional(rollbackFor = Exception.class)
    public void batchDeleteBase(List<String> idList) {
        User user = userService.current();
        for (int i = 0; i < idList.size(); i++) {
            Long id = Long.parseLong(idList.get(i));
            try{
                Base base = baseRepository.findFirstById(id);
                base.setUpdateUser(user);
                base.setIsDeleted(1);
                baseRepository.save(base);
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     * 分页查询基础实体信息
     *
     * @param page
     * @param pageSize
     * @param remark
     */
    public Page<Base> queryBasePage(Integer page, Integer pageSize, String remark){
        Sort.Order order = new Sort.Order(Sort.Direction.DESC, "createTime");
        List<Sort.Order> orders = new ArrayList<>();
        orders.add(order);
        Sort sort = Sort.by(orders);
        PageRequest pageRequest = PageRequest.of(page - 1, pageSize, sort);
        Specification<Base> specification = (root, query, builder) -> {
            List<Predicate> predicateList = new ArrayList<>();
            predicateList.add(builder.equal(root.get("isDeleted"), 0));
            if (StrUtil.isNotEmpty(remark)) {
                predicateList.add(builder.like(root.get("remark"), "%" + remark + "%"));
            }
            Predicate[] arrayType = new Predicate[predicateList.size()];
            return builder.and(predicateList.toArray(arrayType));
        };
        return baseRepository.findAll(specification, pageRequest);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145

BaseDemoController.java

package com.yoyo.admin.web_manage.controller.demo;

import com.yoyo.admin.common.service.demo.BaseDemoService;
import com.yoyo.admin.common.utils.ResultDataUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;

import java.util.Arrays;
import java.util.List;
import java.util.Map;


@Api(tags = "基本增删查改示例")
@RestController
@RequestMapping(value = "/api/demo/base")
public class BaseDemoController {

    private BaseDemoService baseDemoService;

    @Autowired
    public void setBaseDemoService(BaseDemoService baseDemoService) {
        this.baseDemoService = baseDemoService;
    }

    @ApiOperation(value = "新增基础实体", notes = "新增基础实体")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "remark", value = "备注", dataType = "String", required = true, paramType = "body")
    })
    @PostMapping(value = "/createBase")
    public ResponseEntity<?> createBase(@RequestBody @ApiIgnore Map<String, Object> body){
        try {
            return ResultDataUtils.success(baseDemoService.createBase(body));
        } catch (Exception ex) {
            return ResultDataUtils.error(ex.getMessage());
        }
    }

    @ApiOperation(value = "修改基础实体", notes = "修改基础实体")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "id", value = "基础实体id", dataType = "Integer", required = true, paramType = "body"),
            @ApiImplicitParam(name = "remark", value = "备注", dataType = "String", required = true, paramType = "body")
    })
    @PostMapping(value = "/updateBase")
    public ResponseEntity<?> updateBase(@RequestBody @ApiIgnore Map<String, Object> body) {
        try {
            return ResultDataUtils.success(baseDemoService.updateBase(body));
        } catch (Exception ex) {
            return ResultDataUtils.error(ex.getMessage());
        }
    }

    @ApiOperation(value = "批量删除基础实体", notes = "批量删除基础实体")
    @ApiImplicitParam(name = "ids", value = "基础实体id,多个用逗号分隔", required = true, dataType = "String", paramType = "query")
    @GetMapping(value = "/batchDeleteBase")
    public ResponseEntity<?> batchDeleteBase(@RequestParam String ids) {
        try {
            List<String> idList = Arrays.asList(ids.split(","));
            if (CollectionUtils.isEmpty(idList)){
                return ResultDataUtils.error("请勾选至少1个基础实体进行删除");
            }
            baseDemoService.batchDeleteBase(idList);
            return ResultDataUtils.success();
        } catch (Exception ex) {
            return ResultDataUtils.error(ex.getMessage());
        }
    }

    @ApiOperation(value = "分页查询基础实体信息", notes = "分页查询基础实体信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "page", value = "第几页", dataType = "Integer", paramType = "query"),
            @ApiImplicitParam(name = "pageSize", value = "分页大小", dataType = "Integer", paramType = "query"),
            @ApiImplicitParam(name = "remark", value = "备注", dataType = "String", paramType = "body")
    })
    public ResponseEntity<?> queryBasePage(@RequestParam(defaultValue = "1") Integer page,
                                              @RequestParam(defaultValue = "10") Integer pageSize,
                                              @RequestParam(required = false) String remark){
        try {
            return ResultDataUtils.success(baseDemoService.queryBasePage(page, pageSize, remark));
        } catch (Exception ex) {
            return ResultDataUtils.error(ex.getMessage());
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

# 2.5 JPA实现原生查询

有时我们需要使用非托管的entityManager执行原生查询,在JPA环境中执行原生查询(SELECT)的四种方式如下:

  • 使用repository的@Query注解并设置其的nativeQuery等于true。(问题最少,请务必优先选择这种方式)
  • 使用托管的entityManager并unwrap为org.hibernate.SQLQuery。(问题是org.hibernate.SQLQuery已过期,将来的Hibernate版本不再可用,故不推荐)
  • 使用托管的entityManager并unwrap为NativeQueryImpl,需要在方法上加@Transactional(readOnly = true)注解。(如果不加readOnly = true,会有数据回写问题,使得临时赋值操作变得极度危险)
  • 使用非托管的entityManager并unwrap为NativeQueryImpl。(如果非托管的entityManager没有正确关闭,会造成连接泄露,造成连接不可用,所以尽量使用本类提供的方法,不要自行创建非托管的entityManager)

# 2.5.1 使用repository的@Query注解实现原生查询

即上述最推荐的第一种方式。

    /**
     * 原生查询语句示例
     */
    @Query(nativeQuery = true, value = "select city, avg(price) avg_price from spider_new_house where is_deleted = 0 group by city")
    List<Map<String, Object>> queryNewHouseAvgPrice();

    /**
     * 原生增删改语句示例
     */
    @Modifying
    @Query(value = "update spider_new_house set source = ?1 where id = ?2", nativeQuery = true)
    void modifyDataSourceById(String source, Long id);
1
2
3
4
5
6
7
8
9
10
11
12

# 2.5.2 使用非托管的entityManager执行原生查询

即上述的第四种方式,尽量使用以下封装提供的方法。

import org.hibernate.query.internal.NativeQueryImpl;
import org.hibernate.transform.Transformers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class NativeQueryService {

    private EntityManagerFactory entityManagerFactory;

    @Autowired
    public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
    }

    @SuppressWarnings("unchecked")
    public List<Map<String, Object>> queryMapList(String sqlStr, Map<String, Object> params) {
        List<Map<String, Object>> mapList = new ArrayList<>();
        List<?> list = queryList(sqlStr, params);
        for (Object o : list) {
            mapList.add((Map<String, Object>) o);
        }
        return mapList;
    }

    /**
     * 原生查询列表
     * @param sqlStr
     * @param params
     * @return
     */
    public List<?> queryList(String sqlStr, Map<String, Object> params) {
        List<?> list = new ArrayList<>();
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        try {
            Query query = entityManager.createNativeQuery(sqlStr);
            query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
            if (params != null && params.size() > 0) {
                for (Map.Entry<String, Object> entry : params.entrySet()) {
                    query.setParameter(entry.getKey(), entry.getValue());
                }
            }
            list = query.getResultList();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            // 注意:一定要关闭entityManager
            entityManager.close();
        }
        return list;
    }

    /**
     * 原生查询带分页列表
     * @param sqlStr
     * @param params
     * @param page
     * @param pageSize
     * @return
     */
    public List<?> queryPage(String sqlStr, Map<String, Object> params, int page, int pageSize) {
        List<?> list = new ArrayList<>();
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        try {
            Query query = entityManager.createNativeQuery(sqlStr);
            query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
            query.setFirstResult((page - 1) * pageSize);
            query.setMaxResults(pageSize);
            if (params != null && params.size() > 0) {
                for (Map.Entry<String, Object> entry : params.entrySet()) {
                    query.setParameter(entry.getKey(), entry.getValue());
                }
            }
            list = query.getResultList();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            // 注意:一定要关闭entityManager
            entityManager.close();
        }
        return list;
    }

    /**
     * 批量执行SQL
     * @param sqlList
     */
    public void batchExecute(List<String> sqlList) {
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        try {
            entityManager.getTransaction().begin();
            for (String sql : sqlList) {
                entityManager.createNativeQuery(sql).executeUpdate();
            }
            entityManager.getTransaction().commit();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            // 注意:一定要关闭entityManager
            entityManager.close();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

# 2.6 JPA常见问题

[1] @ManyToOne @OneToMany无限递归问题

问题描述:JPA !errorOutstanding" with message transform method call failed at JPLISAgent.c line: 844

解决办法:在 @ManyToOne 的实体类字段上添加@JsonIgnoreProperties@JsonBackReference注解。

@JsonIgnoreProperties({"xxx"}):序列化过程中,忽略对象中的xxx属性  
@JsonBackReference:序列化过程中,忽略整个对象
1
2

注:优先使用 @JsonIgnoreProperties 注解的方案,第二种方案会直接忽略整个对象,查该表时就找不到该对象了。

[2] @OneToMany级联查询

需求描述:查某个表时,自动将@OneToMany注解标识的字段查出。

解决办法:添加 @Fetch(FetchMode.SUBSELECT) 与 fetch = FetchType.EAGER 即可,示例如下:

    @ApiModelProperty(value = "关联摄像头")
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "computer",fetch = FetchType.EAGER)
    @Fetch(FetchMode.SUBSELECT)
    private List<Camera> cameras;
1
2
3
4

[3] Null value was assigned to a property of primitive type setter

无法将null值分配给基本类型,这个异常常会出现在往数据库插入数据时。如果数据库中的字段可以为null,那么实体类中相应的属性应该是一个包装类,如Integer,Long等,而不应该是基本类型int,long。

[4] JPA实体类不能同时使用多个 fetch = FetchType.EAGER 的问题

错误情景:org.hibernate.loader.MultipleBagFetchException:不能同时获取多个包

@OneToMany(mappedBy = "consultaSQL", orphanRemoval = true, fetch = FetchType.EAGER,
        cascade = CascadeType.ALL)
private List<ParametroSQL> parametros;

@OneToMany(mappedBy = "consulta", orphanRemoval = true, fetch = FetchType.EAGER,
        cascade = CascadeType.ALL)
private List<Contato> contatos;
1
2
3
4
5
6
7

解决办法:改成 @LazyCollection(LazyCollectionOption.FALSE) 即可

@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(mappedBy = "consultaSQL", orphanRemoval = true,
        cascade = CascadeType.ALL)
private List<ParametroSQL> parametros;

@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(mappedBy = "consulta", orphanRemoval = true,
        cascade = CascadeType.ALL)
private List<Contato> contatos;
1
2
3
4
5
6
7
8
9

[5] Elasticsearch日期格式的存储与查询问题

问题1:实体类声明是以Date形式存储,但存入ES库却变成了Long类型。

解决办法:在实体类上添加@JSONField(format="yyyy-MM-dd HH:mm:ss")即可解决。

    @ApiModelProperty(value = "采集时间")
    @JSONField(format="yyyy-MM-dd HH:mm:ss")
    private Date collectionTime;
1
2
3

问题2:时间范围查询没有得到预期结果(原因1是ES默认存储的是UTC时间,比北京时间晚8h;原因2是因为被分词处理了)

解决办法:原因1建议直接传入String类型的时间进行查询,原因2精确匹配要加上.keyword(不加是对查询内容做分词处理之后再匹配,加了是直接对查询结果做精确匹配)

        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        if(beginTime != null && endTime != null) {
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("collectionTime.keyword")
                    .gte(beginTimeStr)
                    .lte(endTimeStr);
        }
1
2
3
4
5
6

# 3. Mybatis框架

# 3.1 自动生成mapper和pojo文件

mapper和pojo文件手动编写太过麻烦,可以通过代码生成器去生成。如果你只是想生成pojo文件,那么使用IDEA自带的Database工具即可。如果你除了想生成pojo文件之外还想生成mapper文件,那就需要用到mybatis-generator插件了。

# 3.1.1 通过Database工具自动生成pojo文件

Step1:使用IDEA自带的Database工具填写好信息连接数据库。

Step2:打开连接,选择需要映射生成的POJO类的表,右键,选择Scripted Extensions,然后选择Generate POJOs.groovy,选择生成POJO类的存放位置,点击OK。然后修改一下生成代码里的package路径即可。

# 3.1.2 使用mybatis-generator自动生成mapper和pojo文件

Step1:在maven工程当中的resource下面,创建generatorConfig.xml文件。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!--mysql 连接数据库jar 这里选择自己本地位置-->
    <classPathEntry location="C:\Users\xxx\.m2\repository\mysql\mysql-connector-java\5.1.39\mysql-connector-java-5.1.39.jar" />
    <context id="testTables" targetRuntime="MyBatis3">
        <commentGenerator>
            <!-- 是否去除自动生成的注释 -->
            <property name="suppressAllComments" value="true" />
        </commentGenerator>
        <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://127.0.0.1:3306/testdb" userId="root"
                        password="123456">
        </jdbcConnection>
        <!-- 默认false,把JDBC DECIMAL和NUMERIC类型解析为Integer,为true时解析为java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>
        <!-- targetProject:生成pojo类的位置 -->
        <javaModelGenerator targetPackage="com.test.demo.pojo"
                            targetProject="src/main/java">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
            <!-- 从数据库返回的值被清理前后的空格 -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- targetProject:mapper映射文件生成的位置-->
        <sqlMapGenerator targetPackage="mapper"
                         targetProject="src/main/resources">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>
        <!-- targetPackage:mapper接口生成的位置 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.test.demo.mapper"
                             targetProject="src/main/java">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>
        <!-- 指定数据库表 -->
        <table schema="" tableName="sys_code" domainObjectName="" enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
        </table>
    </context>
</generatorConfiguration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

说明:

  • [1] "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"的字体变成了红色,鼠标放上去显示URI is not registerd。解决办法:鼠标点击红色字,然后Intelli出现小红灯,选择Fetch external resource即可。
  • [2] mysql连接数据库的jar包去maven仓库里找到它的本地位置即可,然后填写数据库连接信息。
  • [3] pojo、mapper以及映射的xml的位置,根据自己的需求填写,最后指定一下数据库表即可。

Step2:配置pom.xml文件,在plugins里增加以下内容:

            <!--mybatis逆向工程-->
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <configuration>
                    <!--配置文件的位置-->
                    <configurationFile>src/main/resources/mybatis-generator.xml</configurationFile>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
                <executions>
                    <execution>
                        <id>Generate MyBatis Artifacts</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.3.2</version>
                    </dependency>
                </dependencies>
            </plugin>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

Step3:打开IDEA的Maven面板,点击Plugins下mybatis-generator的mybatis-generator:generate按钮生成即可。

注:出现BUILD SUCCESS即生成成功,出现No appenders could be found for logger无视即可,生成的文件需要根据需求进行修改。

# 3.2 Mybatis常见问题

[1] Mybatis的特殊符号替换

小于等于    a<=b                 a &lt;= b            a <![CDATA[<= ]]>b
大于等于    a>=b                 a &gt;= b            a <![CDATA[>= ]]>b
不等于      a!=b         <![CDATA[ <> ]]>b            a <![CDATA[!= ]]>b
1
2
3

[2] 启动报错 Exception encountered during context initialization ...... Error creating bean with name 'xxxController' ...

拖到最右边查看具体报错,我是因为复制mapper的xml代码时忘记修改id导致的。

[3] 启动报错Field XXX required a bean of type XXX that could not be found.

可能性1:包结构的问题

  • 项目启动时,只有@SpringBootApplication所在的包被扫描,而其他的controller和service以及mapper在其他的包里,所以并没有被扫描。解决方法是,把启动类放在外层包。

可能性2:没有自动注入导致

  • service类上面没有@service注解或者mapper上没有@Repository注解。但是这种情况比较少见,一般不会忘记。

可能性3:配置了Mybatis,但没有指定扫描的包。

  • 在启动类上加上dao的路径:@MapperScan(value = "com.xxx.dao")

[4] MapStruct出现java: Unknown property问题

原因:mapstruct和lombok冲突了,指定一下lombok的版本即可。

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.5.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.5.0.Final</version>
            <scope>provided</scope>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 4. 与数据库有关的其他框架

# 4.1 Druid数据库连接池

# 4.1.1 Druid是什么

Druid是阿里巴巴的一个开源项目,号称为监控而生的数据库连接池,在功能、性能、扩展性方面都超过其他例如DBCP、C3P0、BoneCP、Proxool、JBoss、DataSource等连接池,而且Druid已经在阿里巴巴部署了超过600个应用,通过了极为严格的考验。

Github项目地址:https://github.com/alibaba/druid (opens new window)

Druid-Monitor

# 4.1.2 整合Druid

Step1:在我们项目的pom.xml文件中添加如下的依赖

<!-- druid -->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.1.10</version>
</dependency>
1
2
3
4
5
6

Step2:配置application.properties文件

## 数据源配置
# 表明使用Druid连接池
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# MySQL驱动
spring.datasource.driverClassName=com.mysql.jdbc.Driver
# 数据库连接信息
spring.datasource.url=jdbc:mysql://localhost:3306/eran?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
# 数据库用户名
spring.datasource.username=root
# 数据库密码
spring.datasource.password=root

## Druid连接池相关属性
# 是否开启监控界面及访问权限(正式环境建议彻底关闭监控页的访问,最起码也要设置强密码的管理员账号限制访问)
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=123456
#初始化时建立物理连接的个数。
spring.datasource.druid.initial-size=5
#最大连接池数量
spring.datasource.druid.max-active=20
#最小连接池数量
spring.datasource.druid.min-idle=5
#获取连接时最大等待时间,单位毫秒
spring.datasource.druid.max-wait=3000
#是否缓存preparedStatement,也就是PSCache,PSCache对支持游标的数据库性能提升巨大,比如说oracle,在mysql下建议关闭。
spring.datasource.druid.pool-prepared-statements=false
#要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
spring.datasource.druid.max-open-prepared-statements= -1
#配置检测可以关闭的空闲连接间隔时间
spring.datasource.druid.time-between-eviction-runs-millis=60000
# 配置连接在池中的最小生存时间
spring.datasource.druid.min-evictable-idle-time-millis= 300000
spring.datasource.druid.max-evictable-idle-time-millis= 400000
#监控统计的stat,以及防sql注入的wall
spring.datasource.druid.filters= stat,wall
#Spring监控AOP切入点,如x.y.z.service.*,配置多个英文逗号分隔
spring.datasource.druid.aop-patterns= com.xxx.xxx.service.*
#是否启用StatFilter默认值true
spring.datasource.druid.web-stat-filter.enabled= true
#添加过滤规则
spring.datasource.druid.web-stat-filter.url-pattern=/*
#忽略过滤的格式
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
#是否启用StatViewServlet默认值true
spring.datasource.druid.stat-view-servlet.enabled= true
#访问路径为/druid时,跳转到StatViewServlet
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
#是否能够重置数据
spring.datasource.druid.stat-view-servlet.reset-enable=false
#需要账号密码才能访问控制台,默认为root
spring.datasource.druid.stat-view-servlet.login-username=root
spring.datasource.druid.stat-view-servlet.login-password=root
#IP白名单
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1
#IP黑名单(共同存在时,deny优先于allow)
spring.datasource.druid.stat-view-servlet.deny=

## Mybatis 配置
#配置为 com.pancm.bean 指向实体类包路径。
mybatis.typeAliasesPackage=com.springboot.bean
#配置为 classpath 路径下 mapper 包下,* 代表会扫描所有 xml 文件。
mybatis.mapperLocations=classpath\:mapper/*.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

直接启动项目,浏览器访问localhost:8080/druid即可看到监控页面。

# 4.1.3 去除Druid的广告

如果使用的是阿里Druid的数据库连接池,那么会自带一个数据库的监控页面,但是其页面底部会有阿里的广告,如下图所示,并且在其最下方的作者申明中,有一个作者的链接,会直接到澳门赌场的页面,这是极其不友好的,因此需要进行去除。

Druid广告

在SpringBoot项目中编写一个RemoveDruidAdConfig配置类,进行监控页面广告的去除:

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.*;
import java.io.IOException;


/**
 * 类名称: RemoveDruidAdConfig
 * 类描述: 去除druid底部的广告配置类
 */

@Configuration
@ConditionalOnWebApplication
@AutoConfigureAfter(DruidDataSourceAutoConfigure.class)
@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true", matchIfMissing = true)
public class RemoveDruidAdConfig {


    /**
     * 方法名: removeDruidAdFilterRegistrationBean
     * 方法描述:  除去页面底部的广告
     * @param properties
     * @return org.springframework.boot.web.servlet.FilterRegistrationBean
     * @throws
     */
    @Bean
    public FilterRegistrationBean removeDruidAdFilterRegistrationBean(DruidStatProperties properties) {
        // 获取web监控页面的参数
        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
        // 提取common.js的配置路径
        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");

        final String filePath = "support/http/resources/js/common.js";

        //创建filter进行过滤
        Filter filter = new Filter() {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
            }

            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                chain.doFilter(request, response);
                // 重置缓冲区,响应头不会被重置
                response.resetBuffer();
                // 获取common.js
                String text = Utils.readFromResource(filePath);
                // 正则替换banner, 除去底部的广告信息
                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
                text = text.replaceAll("powered.*?shrek.wang</a>", "");
                response.getWriter().write(text);
            }

            @Override
            public void destroy() {
            }
        };
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(filter);
        registrationBean.addUrlPatterns(commonJsPattern);
        return registrationBean;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

再次启动项目,可以看到其底部的广告信息已经没有了。

原理说明:之所以底部有广告,是因为其引入的druid jar包的common.js中的内容,这段footer就是广告的来源。

var html ='<footer class="footer">'+
					  '    		<div class="container">'+
					  '<a href="https://render.alipay.com/p/s/taobaonpm_click/druid_banner_click" target="new"><img src="https://render.alipay.com/p/s/taobaonpm_click/druid_banner"></a><br/>' +
				  	  '	powered by <a href="https://github.com/alibaba/" target="_blank">Alibaba</a> & sandzhang & <a href="http://melin.iteye.com/" target="_blank">melin</a> & <a href="https://github.com/shrekwang" target="_blank">shrek.wang</a>'+
				  	  '			</div>'+
					  ' </footer>';
1
2
3
4
5
6

在 RemoveDruidAdConfig 配置类中就是使用过滤器过滤common.js的请求,重新处理后用正则替换相关的广告代码片段。

# 4.2 Screw导出数据库表结构

# 4.2.1 Screw是什么

一句话简介:screw是一个简洁好用的数据库表结构文档生成器

Github项目地址:https://github.com/pingfangushi/screw (opens new window)

# 4.2.2 Screw的特点及支持

特点:[1] 简洁、轻量、设计良好 [2] 多数据库支持 [3] 多种格式文档 [4] 灵活扩展 [5] 支持自定义模板

数据库支持:MySQL、MariaDB、TIDB、Oracle、SqlServer、PostgreSQL、Cache DB

文档生成支持:html、md、word

screw-1

screw-2

# 4.2.3 整合Screw

官网提供了“普通方式”和“Maven插件”两种使用方式,本文只讲“普通方式”的使用方法,以最常见的MySQL和Oracle为例。

Step1:创建Springboot项目并配置好Maven环境

Step2:配置Maven依赖

将以下代码添加到pom.xml文件中,然后打开Maven窗口,点击Lifecycle里的install安装依赖包

注:screw核心的版本号填最新的,点此查看最新版本号 (opens new window)

        <!-- screw核心 -->
        <dependency>
            <groupId>cn.smallbun.screw</groupId>
            <artifactId>screw-core</artifactId>
            <version>1.0.5</version>
        </dependency>
        <!-- HikariCP -->
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>3.4.5</version>
        </dependency>
        <!--mysql driver-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
        </dependency>
        <!--oracle driver-->
        <dependency>
            <groupId>com.oracle.ojdbc</groupId>
            <artifactId>ojdbc8</artifactId>
            <version>19.3.0.0</version>
        </dependency>
        <dependency>
            <groupId>cn.easyproject</groupId>
            <artifactId>orai18n</artifactId>
            <version>12.1.0.2.0</version>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

Step3:编写数据库设计文档的生成代码

在任意地方创建一个测试类ScrewConfig.java,编写数据库设计文档的生成代码如下:

package com.example.screw;

import cn.smallbun.screw.core.Configuration;
import cn.smallbun.screw.core.engine.EngineConfig;
import cn.smallbun.screw.core.engine.EngineFileType;
import cn.smallbun.screw.core.engine.EngineTemplateType;
import cn.smallbun.screw.core.execute.DocumentationExecute;
import cn.smallbun.screw.core.process.ProcessConfig;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import javax.sql.DataSource;
import java.util.ArrayList;

public class ScrewConfig {
    /**
     * 数据库设计文档生成
     */
    static void documentGeneration() {
        //数据源
        HikariConfig hikariConfig = new HikariConfig();
        //数据库driver
        hikariConfig.setDriverClassName("oracle.jdbc.driver.OracleDriver"); //oracle
        //hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver"); //mysql
        //数据库URL
        hikariConfig.setJdbcUrl("jdbc:oracle:thin:@IP地址:端口:数据库名?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=CTT");
        //hikariConfig.setJdbcUrl("jdbc:mysql://IP地址:端口/数据库名?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=CTT");
        //数据库用户名
        hikariConfig.setUsername("root");
        //数据库用户密码
        hikariConfig.setPassword("password");
        //设置可以获取tables remarks信息
        hikariConfig.addDataSourceProperty("useInformationSchema", "true");
        hikariConfig.setMinimumIdle(2);
        hikariConfig.setMaximumPoolSize(5);
        DataSource dataSource = new HikariDataSource(hikariConfig);
        //生成配置
        EngineConfig engineConfig = EngineConfig.builder()
                //生成文件路径
                .fileOutputDir("../doc")
                //打开目录
                .openOutputDir(true)
                //文件类型 HTML、MD、WORD
                .fileType(EngineFileType.HTML)
                //生成模板实现
                .produceType(EngineTemplateType.freemarker)
                //自定义文件名称
                .fileName("demo").build();

        //忽略表
        ArrayList<String> ignoreTableName = new ArrayList<>();
        ignoreTableName.add("test_user");
        ignoreTableName.add("test_group");
        //忽略表前缀
        ArrayList<String> ignorePrefix = new ArrayList<>();
        ignorePrefix.add("test_");
        //忽略表后缀
        ArrayList<String> ignoreSuffix = new ArrayList<>();
        ignoreSuffix.add("_test");
        ProcessConfig processConfig = ProcessConfig.builder()
                //指定生成逻辑、当存在指定表、指定表前缀、指定表后缀时,将生成指定表,其余表不生成、并跳过忽略表配置
                //根据名称指定表生成
                .designatedTableName(new ArrayList<>())
                //根据表前缀生成
                .designatedTablePrefix(new ArrayList<>())
                //根据表后缀生成
                .designatedTableSuffix(new ArrayList<>())
                //忽略表名
                .ignoreTableName(ignoreTableName)
                //忽略表前缀
                .ignoreTablePrefix(ignorePrefix)
                //忽略表后缀
                .ignoreTableSuffix(ignoreSuffix).build();
        //配置
        Configuration config = Configuration.builder()
                //版本
                .version("v1.0")
                //数据源
                .dataSource(dataSource)
                //生成配置
                .engineConfig(engineConfig)
                //生成配置
                .produceConfig(processConfig)
                .build();
        //执行生成
        new DocumentationExecute(config).execute();
    }

    public static void main(String[] args) {
        documentGeneration();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

注:如果项目里的MySQL是5.x,驱动处则为com.mysql.jdbc.Driver

Step4:运行screw生成数据库设计文档

直接运行ScrewConfig.java代码,即可生成数据库设计文档。

# 5. 参考资料

[1] screw:简洁好用的数据库表结构文档生成器 from Github (opens new window)

[2] 实用!一键生成数据库文档,堪称数据库界的Swagger from 51CTO (opens new window)

[3] 超给力,一键生成数据库文档-数据库表结构逆向工程 from 博客园 (opens new window)

[4] 使用 screw 数据库文档生成工具快速生成数据库文档 from Blilibili (opens new window)

[5] Spring boot学习(四)Spring boot整合Druid from 掘金 (opens new window)

[6] SpringBoot项目去除druid监控的阿里广告 from CSDN (opens new window)

[7] SpringBoot 关闭druid的页面 from CSDN (opens new window)

[8] Exception encountered during context initialization报错解决 from segmentfault (opens new window)

[9] 通过IDEA将数据库表生成对应的pojo类_Aliux_JLQ的博客 from 程序员宝宝 (opens new window)

[10] IDEA中使用mybatis-generator自动生成mapper和pojo文件 from CSDN (opens new window)

[11] mybatis-generator 去除example from CSDN (opens new window)

[12] SpringBoot2.x系列教程29--SpringBoot整合JPA框架实现数据库CRUD操作 from 阿里云 (opens new window)

[13] Spring Data JPA了解常用注解和属性 from CSDN (opens new window)

[14] jpa实体 @ManyToOne @OneToMany无限递归 from CSDN (opens new window)

[15] 记录使用Mapstruct和Lombook后无法编译通过的问题 from 简书 (opens new window)

Last Updated: 10/6/2024, 4:02:36 PM