知了小站 - IT人的小站 - jpa 2023-05-12T16:32:00+08:00 Typecho https://www.ydyno.com/feed/atom/tag/jpa/ <![CDATA[Jpa 持久层中使用自定义对象接收数据]]> https://www.ydyno.com/archives/1739.html 2023-05-12T16:32:00+08:00 2023-05-12T16:32:00+08:00 知了小站 https://www.ydyno.com 在 JPA 持久层中,可以自定义接收数据的对象。这通常用于查询操作,其中查询结果不完全匹配现有的实体类,或者需要仅返回某些字段的结果。以下示例,展示如何在 JPA 持久层中自定义接收数据的对象

假设有一个名为 Person 的实体类,包含 id、name 和 age 字段:

@Entity
@Table(name = "person")
public class Person {
  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    private int age;
    
    // 省略构造函数、getter 和 setter 方法
}

现在我们只想查询人员的姓名和年龄,并将结果封装到自定义的数据对象 PersonInfo 中:

public class PersonInfo {
  
    private String name;
    
    private int age;
    
    // 省略构造函数、getter 和 setter 方法
}

第一种方式

@Repository
public class PersonRepository {
    @PersistenceContext
    private EntityManager entityManager;
    
    public List<PersonInfo> getPersonInfo() {
        String query = "SELECT new com.example.PersonInfo(p.name, p.age) FROM Person p";
        TypedQuery<PersonInfo> typedQuery = entityManager.createQuery(query, PersonInfo.class);
        return typedQuery.getResultList();
    }
}

在上面的代码中,我们使用 SELECT new 关键字创建了一个 PersonInfo 对象,并将查询结果映射到该对象。通过使用构造函数,可以选择性地指定要接收的字段。

第二种方式

使用 Spring Data JPA 的 Repository 接口

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
    @Query("SELECT new com.example.PersonInfo(p.name, p.age) FROM Person p")
    List<PersonInfo> getPersonInfo();
}

在上面的示例中,我们使用了 @Query 注解,并指定了一个自定义的查询语句。在查询语句中,我们使用了 new 关键字创建了一个 PersonInfo 对象,并将查询结果映射到该对象。

注意点

在查询语句中,com.example.PersonInfo 是 PersonInfo 类的完全限定名,确保使用正确的包名,确保创建了对应的构造方法。

]]>
<![CDATA[Jpa进阶,使用 Specification 进行高级查询]]> https://www.ydyno.com/archives/1583.html 2022-06-08T21:45:00+08:00 2022-06-08T21:45:00+08:00 知了小站 https://www.ydyno.com 前言

上一篇文章主要讲了 Jpa 的简单使用,而在实际项目中并不能满足我们的需求。如对多张表的关联查询,以及查询时需要的各种条件,这个时候你可以使用自定义 SQL 语句,但是Jpa并不希望我们这么做,于是就有了一个扩展:使用 Specification 进行查询

修改相应代码

1、修改 User.class

代码用的上一篇文章的,这里在 User 类中进行扩展,待会查询时会用到

@Entity
@Table(name = "user")
public class User {

    //部分代码略

    /**
     *  加上该注解,在保存该实体时,Jpa将为我们自动设置上创建时间
     */
    @CreationTimestamp
    private Timestamp createTime;

    /**
     *  加上该注解,在保存或者修改该实体时,Jpa将为我们自动创建时间或更新日期
     */
    @UpdateTimestamp
    private Timestamp updateTime;

    /**
     * 关联角色,测试多表查询
     */
    @ManyToOne
    @JoinColumn(name = "role_id")
    private Role role;

    //部分代码略  
}
2、新增Role.class
@Entity
@Table(name = "role")
public class Role {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true,nullable = false)
    private String name;

    //get set略  
}
3、修改UserRepository

要使用 Specification,需要继承 JpaSpecificationExecutor 接口,修改后的代码如下

public interface UserRepo extends JpaRepository<User,Long>, JpaSpecificationExecutor {

}
4、查看 JpaSpecificationExecutor 源码

Specification 是 Spring Data JPA 提供的一个查询规范,这里所有的操作都是围绕 Specification 来进行

public interface JpaSpecificationExecutor<T> {
    Optional<T> findOne(@Nullable Specification<T> var1);

    List<T> findAll(@Nullable Specification<T> var1);

    Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);

    List<T> findAll(@Nullable Specification<T> var1, Sort var2);

    long count(@Nullable Specification<T> var1);
}

封装查询Service

我这里简单做了下简单封装,编写 UserQueryService.class

@Service
public class UserQueryService {

    @Autowired
    private UserRepo userRepo;

    /**
     * 分页加高级查询
     */
    public Page queryAll(User user, Pageable pageable , String roleName){
        
        return userRepo.findAll(new UserSpec(user,roleName),pageable);
    }

    /**
     * 不分页
     */
    public List queryAll(User user){
        
        return userRepo.findAll(new UserSpec(user));
    }

    class UserSpec implements Specification<User>{

        private User user;

        private String roleName;

        public UserSpec(User user){
            this.user = user;
        }

        public UserSpec(User user,String roleName){
            this.user = user;
            this.roleName = roleName;
        }

        @Override
        public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {

            List<Predicate> list = new ArrayList<Predicate>();

            /**
             * 左连接,关联查询
             */
            Join<Role,User> join = root.join("role",JoinType.LEFT);

            if(!StringUtils.isEmpty(user.getId())){
                /**
                 * 相等
                 */
                list.add(cb.equal(root.get("id").as(Long.class),user.getId()));
            }

            if(!StringUtils.isEmpty(user.getUsername())){
                /**
                 * 模糊
                 */
                list.add(cb.like(root.get("username").as(String.class),"%"+user.getUsername()+"%"));
            }

            if(!StringUtils.isEmpty(roleName)){

                /**
                 * 这里的join.get("name"),就是对应的Role.class里面的name
                 */
                list.add(cb.like(join.get("name").as(String.class),"%"+roleName+"%"));
            }

            if(!StringUtils.isEmpty(user.getCreateTime())){

                /**
                 * 大于等于
                 */
                list.add(cb.greaterThanOrEqualTo(root.get("createTime").as(Timestamp.class),user.getCreateTime()));
            }

            if(!StringUtils.isEmpty(user.getUpdateTime())){

                /**
                 * 小于等于
                 */
                list.add(cb.lessThanOrEqualTo(root.get("createTime").as(Timestamp.class),user.getUpdateTime()));

            }

            Predicate[] p = new Predicate[list.size()];
            return cb.and(list.toArray(p));
        }
    }
}

查询测试

1、新增测试数据
    @Test
    public void test3() {

        /**
         * 新增角色
         */
        Role role = new Role();
        role.setName("测试角色");
        role = roleRepo.save(role);

        /**
         * 新增并绑定角色
         */
        User user = new User("小李",20,"男",role);
        User user1 = new User("小花",21,"女",role);
        
        userRepo.save(user);
        userRepo.save(user1);
    }

查看数据都已经新增成功了,并且 createTime 和 updateTime 也帮我们加上了

l45n48n4.png

2、简单查询
    @Test
    public void Test4(){

        /**
         * 添加查询数据,模糊查询用户名
         */
        User user = new User();
        user.setUsername("花");
        List<User> users = userQueryService.queryAll(user);
        users.forEach(user1 -> {
            System.out.println(user1.toString());
        });
    }

运行结果如下

l45n59zf.png

3、分页+关联查询

    @Test
    public void test5() {

        //页码,Pageable中默认是从0页开始
        int page = 0;
        //每页的个数
        int size = 10;
        Sort sort = new Sort(Sort.Direction.DESC,"id");
        Pageable pageable = PageRequest.of(page,size,sort);

        Page<User> users = userQueryService.queryAll(new User(),pageable,"测试角色");

        System.out.println("总数据条数:"+users.getTotalElements());
        System.out.println("总页数:"+users.getTotalPages());
        System.out.println("当前页数:"+users.getNumber());

        users.forEach(user1 -> {
            System.out.println(user1.toString());
        });

    }
}

通过角色的名称查询用户,运行结果如下

l45n5tz2.png

]]>
<![CDATA[JPA入门,Spring Boot 整合 JPA 操作数据库]]> https://www.ydyno.com/archives/1571.html 2022-06-01T20:23:00+08:00 2022-06-01T20:23:00+08:00 知了小站 https://www.ydyno.com 简单了解

Jpa(java Persistence API,java持久化 api),它定义了对象关系映射(ORM)以及实体对象持久化的标准接口。在 Spring boot中 JPA 是依靠 Hibernate才得以实现对的,Hibernate 在 3.2 版本中对 JPA 的实现有了完全的支持。
Spring Boot 整合 JPA 可使开发者用极简的代码实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!

添加依赖

#这里添加 Jpa 和 Mysql 的依赖
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

开发Jpa

编写实体类

定义用户实体类 User

//@Entity 表明这个是一个实体类
@Entity
//指定表名
@Table(name = "user")
public class User {

    /**
     * 表明这个字段是主键,并且ID是自增的
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * 这样则表示该属性,在数据库中的名称是 username,并且使唯一的且不能为空的
     */
    @Column(name = "username",unique = true,nullable = false)
    private String username;

    private Integer age;

    private String sex;

    //get set略
}

配置文件说明

Spring Boot 配置文件 application.yml 内容如下

server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/jpa
    username: root
    password: 123456

  jpa:
    hibernate:
      #注入方式
      ddl-auto: update
      naming:
        #Hibernate 命名策略,这里修改下
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

    properties:
      hibernate:
        #数据库方言
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect

ddl-auto属性说明

l45bhlc3.png

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

这里我们使用 update,让应用启动时自动给我们生成 User 表

基础操作

1、编写 UserRepo 继承 JpaRepository
import me.zhengjie.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepo extends JpaRepository<User,Long> {
    
}
2、使用默认方法

在 test 目录中,新建 UserTests


@RunWith(SpringRunner.class)
@SpringBootTest
public class UserTests {

    @Autowired
    private UserRepo userRepo;

    @Test
    public void test1() {

        User user=new User();
        //查询全部
        List<User> userList = userRepo.findAll();

        //根据ID查询
        Optional<User> userOptional = userRepo.findById(1L);

        //保存,成功后会返回成功后的结果
        user = userRepo.save(user);

        //删除
        userRepo.delete(user);
        //根据ID删除
        userRepo.deleteById(1L);

        //计数
        Long count = userRepo.count();
        
        //验证是否存在
        Boolean b = userRepo.existsById(1l);
    }

}

自定义简单查询

自定义的简单查询就是根据方法名来自动生成 SQL,主要的语法是 findXXBy, readAXXBy, queryXXBy, countXXBy, getXXBy 后面跟属性名称:

public interface UserRepo extends JpaRepository<User,Long> {

    /**
     * 根据 username 查询
     * @param username
     * @return
     */
    User findByUsername(String username);

    /**
     * 根据 username 和 age 查询
     * @param username
     * @param age
     * @return
     */
    User findByUsernameAndAge(String username,Integer age);
}

具体的关键字,使用方法和生产成 SQL 如下表所示

KeywordSampleJPQL snippet
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age ⇐ ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection age)… where x.age not in ?1
TRUEfindByActiveTrue()… where x.active = true
FALSEfindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)

分页查询

Page<User> findALL(Pageable pageable);
    
Page<User> findByUserName(String userName,Pageable pageable);

Pageable 是 spring 封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则

@Test
public void test2() {
    //页码,Pageable中默认是从0页开始
    int page = 0;

    //每页的个数
    int size = 10;
    Sort sort = new Sort(Sort.Direction.DESC,"id");
    Pageable pageable = PageRequest.of(page,size,sort);
    Page<User> list = userRepo.findAll(pageable);
}

限制查询

有时候我们只需要查询前N个元素

     /**
     * 限制查询
     */
    List<User> queryFirstByAge(Integer age);

    List<User> queryFirst10ByAge(Integer age);

自定义SQL

如果项目中由于某些原因 Jpa 自带的已经满足不了我们的需求了,这个时候我们就可以自定义的 SQL 来查询,只需要在 SQL 的查询方法上面使用@Query注解,如涉及到删除和修改在需要加上 @Modifying

    /**
     * 自定义SQL,nativeQuery = true,表明使用原生sql
     */
    @Modifying
    @Query(value = "update User u set u.userName = ?1 where u.id = ?2",nativeQuery = true)
    void modifyUsernameById(String userName, Long id);

    @Modifying
    @Query(value = "delete from User where id = ?1",nativeQuery = true)
    void deleteByUserId(Long id);

    @Query(value = "select u from User u where u.id = ?1",nativeQuery = true)
    User findByUserId(Long id);

本文主要讲解了 Jpa 的一些简单的操作,下篇文章将讲解 Jpa 如何使用 Specification 实现复杂的查询,如多表查询,模糊查询,日期的查询等

]]>
<![CDATA[Spring boot 整合 FreeMarker 实现代码生成功能]]> https://www.ydyno.com/archives/1177.html 2019-10-17T17:14:00+08:00 2019-10-17T17:14:00+08:00 知了小站 https://www.ydyno.com 在我们开发一个新的功能的时候,会根据表创建Entity,Controller,Service,Repository等代码,其中很多步骤都是重复的,并且特别繁琐。这个时候就需要一个代码生成器帮助我们解决这个问题从而提高工作效率,让我们更致力于业务逻辑。

设计原理

在我们安装数据库后会有几个默认的数据库,其中information_schema这个数据库中保存了MySQL服务器所有数据库的信息,如:数据库名、数据库表、表的数据信息与访问权限等。

information_schema的表tables记录了所有数据库的表的信息
information_schema的表columns记录了所有数据库的表字段详细的信息

我们代码中可以可以通过Sql语句查询出当前数据库中所有表的信息,这里已 eladmin 为例。

# 显示部分数据:表名称、数据库引擎、编码、表备注、创建时间
select table_name ,create_time , engine, table_collation, table_comment  from information_schema.tables 
where table_schema = (select database());

QQ截图20191018201655.png
知道表的数据后,可以查询出表字段的详细数据,这里用 job 表为例

sql语句如下:

# 显示部分数据:字段名称、字段类型、字段注释、字段键类型等
select column_name, is_nullable, data_type, column_comment, column_key, extra from information_schema.columns 
where table_schema = (select database()) and table_name = "job";

QQ截图20191018202200.png
有了表字段信息的数据后,通过程序将数据库表字段类型转换成Java语言的字段类型,再通过FreeMarker创建模板,将数据写入到模板,输出成文件即可实现代码生成功能。

代码实现

这里只贴出核心代码,源码可查询文末地址,首先创建一个新的spring boot 项目,选择如下依赖
QQ截图20191018210142.png
Maven完整依赖如下

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <!--   配置管理工具     -->
        <dependency>
            <groupId>commons-configuration</groupId>
            <artifactId>commons-configuration</artifactId>
            <version>1.9</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

项目结构如下
QQ截图20191019102804.png

教程开始

修改Spring boot 配置文件 application.yml,如下

service:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/eladmin?serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
  jpa:
    show-sql: true

在 resources 目录下创建 Mysql 字段与 Java字段对应关系的配置文件 generator.properties,生成代码时字段转换时使用

tinyint=Integer
smallint=Integer
mediumint=Integer
int=Integer
integer=Integer

bigint=Long

float=Float

double=Double

decimal=BigDecimal

bit=Boolean

char=String
varchar=String
tinytext=String
text=String
mediumtext=String
longtext=String

date=Timestamp
datetime=Timestamp
timestamp=Timestamp

在 vo 包下创建临时 Vo 类 ColumnInfo,该类的功能用于接收Mysql字段详细信息

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class ColumnInfo {

    /** 数据库字段名称 **/
    private Object columnName;

    /** 允许空值 **/
    private Object isNullable;

    /** 数据库字段类型 **/
    private Object columnType;

    /** 数据库字段注释 **/
    private Object columnComment;

    /** 数据库字段键类型 **/
    private Object columnKey;

    /** 额外的参数 **/
    private Object extra;
}

在 util 包下创建字段工具类 ColumnUtil,该类的功能用于转换mysql类型为Java字段类型,同时添加驼峰转换方法,将表名转换成类名

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;

/**
 * sql字段转java
 *
 * @author jie
 * @date 2019-01-03
 */
public class ColumnUtil {

    private static final char SEPARATOR = '_';

    /**
     * 获取配置信息
     */
    public static PropertiesConfiguration getConfig() {
        try {
            return new PropertiesConfiguration("generator.properties");
        } catch (ConfigurationException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 转换mysql数据类型为java数据类型
     * @param type
     * @return
     */
    public static String cloToJava(String type){
        Configuration config = getConfig();
        return config.getString(type,null);
    }

    /**
     * 驼峰命名法工具
     *
     * @return toCamelCase(" hello_world ") == "helloWorld"
     * toCapitalizeCamelCase("hello_world") == "HelloWorld"
     * toUnderScoreCase("helloWorld") = "hello_world"
     */
    public static String toCamelCase(String s) {
        if (s == null) {
            return null;
        }
        s = s.toLowerCase();
        StringBuilder sb = new StringBuilder(s.length());
        boolean upperCase = false;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);

            if (c == SEPARATOR) {
                upperCase = true;
            } else if (upperCase) {
                sb.append(Character.toUpperCase(c));
                upperCase = false;
            } else {
                sb.append(c);
            }
        }

        return sb.toString();
    }

    /**
     * 驼峰命名法工具
     *
     * @return toCamelCase(" hello_world ") == "helloWorld"
     * toCapitalizeCamelCase("hello_world") == "HelloWorld"
     * toUnderScoreCase("helloWorld") = "hello_world"
     */
    public static String toCapitalizeCamelCase(String s) {
        if (s == null) {
            return null;
        }
        s = toCamelCase(s);
        return s.substring(0, 1).toUpperCase() + s.substring(1);
    }
}

在 util 包下创建代码生成工具类 GeneratorUtil,该类用于将获取到的Mysql字段信息转出Java字段类型,并且获取代码生成的路径,读取 Template,并且输出成文件,代码如下:

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import java.io.*;
import java.time.LocalDate;
import java.util.*;

/**
 * 代码生成
 *
 * @author jie
 * @date 2019-01-02
 */
@Slf4j
public class GeneratorUtil {

    private static final String TIMESTAMP = "Timestamp";

    private static final String BIGDECIMAL = "BigDecimal";

    private static final String PK = "PRI";

    private static final String EXTRA = "auto_increment";


    /**
     * 生成代码
     * @param columnInfos
     * @param pack
     * @param author
     * @param tableName
     * @throws IOException
     */
    public static void generatorCode(List<ColumnInfo> columnInfos, String pack, String author, String tableName) throws IOException {
        Map<String, Object> map = new HashMap<>();
        map.put("package", pack);
        map.put("author", author);
        map.put("date", LocalDate.now().toString());
        map.put("tableName", tableName);
        // 转换为小写开头的的类名, hello_world == helloWorld
        String className = ColumnUtil.toCapitalizeCamelCase(tableName);
        // 转换为大写开头的类名, hello_world == HelloWorld
        String changeClassName = ColumnUtil.toCamelCase(tableName);

        map.put("className", className);
        map.put("changeClassName", changeClassName);
        // 是否包含 Timestamp 类型
        map.put("hasTimestamp", false);
        // 是否包含 BigDecimal 类型
        map.put("hasBigDecimal", false);
        // 是否为自增主键
        map.put("auto", false);

        List<Map<String, Object>> columns = new ArrayList<>();
        for (ColumnInfo column : columnInfos) {
            Map<String, Object> listMap = new HashMap<>();
            listMap.put("columnComment", column.getColumnComment());
            listMap.put("columnKey", column.getColumnKey());

            String colType = ColumnUtil.cloToJava(column.getColumnType().toString());
            String changeColumnName = ColumnUtil.toCamelCase(column.getColumnName().toString());
            if (PK.equals(column.getColumnKey())) {
                map.put("pkColumnType", colType);
                map.put("pkChangeColName", changeColumnName);
            }
            if (TIMESTAMP.equals(colType)) {
                map.put("hasTimestamp", true);
            }
            if (BIGDECIMAL.equals(colType)) {
                map.put("hasBigDecimal", true);
            }
            if (EXTRA.equals(column.getExtra())) {
                map.put("auto", true);
            }
            listMap.put("columnType", colType);
            listMap.put("columnName", column.getColumnName());
            listMap.put("isNullable", column.getIsNullable());
            listMap.put("changeColumnName", changeColumnName);
            columns.add(listMap);
        }
        map.put("columns", columns);
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_23);
        configuration.setClassForTemplateLoading(GeneratorUtil.class, "/template");
        Template template = configuration.getTemplate("Entity.ftl");
        // 获取文件路径
        String filePath = getAdminFilePath(pack, className);
        File file = new File(filePath);
        // 生成代码
        genFile(file, template, map);
    }

    /**
     * 定义文件路径以及名称
     */
    private static String getAdminFilePath(String pack, String className) {
        String ProjectPath = System.getProperty("user.dir") + File.separator;
        String packagePath = ProjectPath + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator;
        if (!ObjectUtils.isEmpty(pack)) {
            packagePath += pack.replace(".", File.separator) + File.separator;
        }
        return packagePath + "entity" + File.separator + className + ".java";
    }

    private static void genFile(File file, Template template, Map<String, Object> params) throws IOException {
        File parentFile = file.getParentFile();
        // 创建目录
        if (null != parentFile && !parentFile.exists()) {
            parentFile.mkdirs();
        }
        //创建输出流
        Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
        //输出模板和数据模型都对应的文件
        try {
            template.process(params, writer);
        } catch (TemplateException e) {
            e.printStackTrace();
        }
    }
}

在 resources 的 template 目录下创建 framework 模板 Entity.ftl,代码如下:

package ${package}.entity;

import lombok.Data;
import javax.persistence.*;
<#if hasTimestamp>
import java.sql.Timestamp;
</#if>
<#if hasBigDecimal>
import java.math.BigDecimal;
</#if>
import java.io.Serializable;

/**
* @author ${author}
* @date ${date}
*/
@Entity
@Data
@Table(name="${tableName}")
public class ${className} implements Serializable {
<#if columns??>
    <#list columns as column>

    <#if column.columnComment != ''>
    // ${column.columnComment}
    </#if>
    <#if column.columnKey = 'PRI'>
    @Id
    <#if auto>
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    </#if>
    </#if>
    @Column(name = "${column.columnName}"<#if column.columnKey = 'UNI'>,unique = true</#if><#if column.isNullable = 'NO' && column.columnKey != 'PRI'>,nullable = false</#if>)
    private ${column.columnType} ${column.changeColumnName};
    </#list>
</#if>
}

创建服务类 GeneratorService,该类用于获取数据库表的源数据

import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 代码生成服务
 */
@Service
public class GeneratorService {

    @PersistenceContext
    private EntityManager em;

    public List<ColumnInfo> getColumns(String tableName) {
        StringBuilder sql = new StringBuilder("select column_name, is_nullable, data_type, column_comment, column_key, extra from information_schema.columns where ");
        if(!ObjectUtils.isEmpty(tableName)){
            sql.append("table_name = '").append(tableName).append("' ");
        }
        sql.append("and table_schema = (select database()) order by ordinal_position");
        Query query = em.createNativeQuery(sql.toString());
        List result = query.getResultList();
        List<ColumnInfo> columnInfos = new ArrayList<>();
        for (Object o : result) {
            Object[] obj = (Object[])o;
            columnInfos.add(new ColumnInfo(obj[0],obj[1],obj[2],obj[3],obj[4],obj[5]));
        }
        return columnInfos;
    }
}

由于没有前端页面,所以只能在测试类中演示代码生成功能,GeneratorDomeApplicationTests 修改如下

import com.ydyno.util.GeneratorUtil;
import com.ydyno.vo.ColumnInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.util.List;

@SpringBootTest
class GeneratorDomeApplicationTests {

    @Autowired
    private GeneratorService generatorService;

    @Test
    void genTest() throws IOException {
        String tableName = "job";
        String pack = "com.ydyno";
        String author = "Zheng Jie";
        List<ColumnInfo> columnInfos = generatorService.getColumns(tableName);
        GeneratorUtil.generatorCode(columnInfos,pack,author,tableName);
    }

}

执行后,查看创建好的Entity

]]>