桃花岛--SpringBoot系列之Spring Data Jpa注解和基础操作

本章小宋讲一下Spring Data Jpa的一些常用注解和一些简单的基础操作。

JPA注解和简单操作

这里小宋讲解一下Spring Data Jpa的相关知识。

1.相关依赖

	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
	</dependencies>

2.配置数据库连接信息和JPA配置

下面的配置中要先去单独说一下 spring.jpa.hibernate.ddl-auto=create这个配置选项。
该属性常用的选项包含4种:

  1. create:每次重新启动项目都会重新创建新表结构,会导致数据丢失
  2. create-drop:每次启动项目创建表结构,关闭项目删除表结构
  3. update:每次启动项目会更新表结构
  4. validate:验证表结构,不对数据库进行任何更改

一定要不要在生产环境使用 ddl 自动生成表结构,一般推荐手写 SQL 语句配合 Flyway 来做这些事情。

spring.datasource.url=jdbc:mysql://localhost:3306/springboot_jpa?useSSL=false&serverTimezone=CTT
spring.datasource.username=root
spring.datasource.password=123456
# 打印出 sql 语句
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create
spring.jpa.open-in-view=false
# 创建的表的 ENGINE 为 InnoDB
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL55Dialect

3.实体类

我们为这个类添加 @Entity 注解代表它是数据库持久化类,并配置主键 id。

import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Data
@NoArgsConstructor
publicclass Person {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true)
    private String name;
    private Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

}

3.1 创建表

@Entity:声明一个类对应一个数据库实体。
@Table:设置表名
@NoArgsConstructor:无参构造方法

@Entity
@Data
@NoArgsConstructor
publicclass Person {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(unique = true)
    private String name;
    private Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

3.2 创建主键

@Id:声明一个字段为主键。

使用@Id声明之后,我们还需要定义主键的生成策略。我们可以使用 @GeneratedValue 指定主键生成策略。

1.通过 @GeneratedValue直接使用 JPA 内置提供的四种主键生成策略来指定主键生成策略。

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

JPA使用枚举定义了4种常见的主键生成策略:

public enum GenerationType {
    /**
     * 使用一个特定的数据库表格来保存主键
     * 持久化引擎通过关系数据库的一张特定的表格来生成主键,
     */
    TABLE,

    /**
     *在某些数据库中,不支持主键自增长,比如Oracle、PostgreSQL其提供了一种叫做"序列(sequence)"的机制生成主键
     */
    SEQUENCE,

    /**
     * 主键自增长
     */
    IDENTITY,

    /**
     *把主键生成策略交给持久化引擎(persistence engine),
     *持久化引擎会根据数据库在以上三种主键生成 策略中选择其中一种
     */
    AUTO
}

@GeneratedValue注解默认使用的策略是GenerationType.AUTO

public @interface GeneratedValue {

    GenerationType strategy() default AUTO;
    String generator() default "";
}

一般使用 MySQL 数据库的话,使用GenerationType.IDENTITY策略比较普遍一点(分布式系统的话需要另外考虑使用分布式 ID)。
2.通过 @GenericGenerator声明一个主键策略,然后 @GeneratedValue使用这个策略

@Id
@GeneratedValue(generator = "IdentityIdGenerator")
@GenericGenerator(name = "IdentityIdGenerator", strategy = "identity")
private Long id;

等同于:

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

jpa 提供的主键生成策略有如下一些:

public class DefaultIdentifierGeneratorFactory
        implements MutableIdentifierGeneratorFactory, Serializable, ServiceRegistryAwareService {

    @SuppressWarnings("deprecation")
    public DefaultIdentifierGeneratorFactory() {
        register( "uuid2", UUIDGenerator.class );
        register( "guid", GUIDGenerator.class );            // can be done with UUIDGenerator + strategy
        register( "uuid", UUIDHexGenerator.class );            // "deprecated" for new use
        register( "uuid.hex", UUIDHexGenerator.class );     // uuid.hex is deprecated
        register( "assigned", Assigned.class );
        register( "identity", IdentityGenerator.class );
        register( "select", SelectGenerator.class );
        register( "sequence", SequenceStyleGenerator.class );
        register( "seqhilo", SequenceHiLoGenerator.class );
        register( "increment", IncrementGenerator.class );
        register( "foreign", ForeignGenerator.class );
        register( "sequence-identity", SequenceIdentityGenerator.class );
        register( "enhanced-sequence", SequenceStyleGenerator.class );
        register( "enhanced-table", TableGenerator.class );
    }

    public void register(String strategy, Class generatorClass) {
        LOG.debugf( "Registering IdentifierGenerator strategy [%s] -> [%s]", strategy, generatorClass.getName() );
        final Class previous = generatorStrategyToClassNameMap.put( strategy, generatorClass );
        if ( previous != null ) {
            LOG.debugf( "    - overriding [%s]", previous.getName() );
        }
    }

}

3.3 设置字段类型

@Column:声明字段。

示例:
设置属性 userName 对应的数据库字段名为 user_name,长度为 32,非空

@Column(name = "user_name", nullable = false, length=32)
private String userName;

设置字段类型并且加默认值,这个还是挺常用的。

@Column(columnDefinition = "tinyint(1) default 1")
private Boolean enabled;

3.4 指定不持久化特定字段

@Transient :声明不需要与数据库映射的字段,在保存的时候不需要保存进数据库 。

如果我们想让secrect 这个字段不被持久化,可以使用 @Transient关键字声明。

@Entity(name="USER")
public class User {

    ......
    @Transient
    private String secrect; // not persistent because of @Transient

}

除了 @Transient关键字声明, 还可以采用下面3种方法:

static String secrect; // not persistent because of static
final String secrect = “Satish”; // not persistent because of final
transient String secrect; // not persistent because of transient

但是一般注解的方式使用的比较多

3.5 声明大字段

@Lob:声明某个字段为大字段。

@Lob
private String content;
@Lob
//指定 Lob 类型数据的获取策略, FetchType.EAGER 表示非延迟 加载,而 FetchType. LAZY 表示延迟加载 ;
@Basic(fetch = FetchType.EAGER)
//columnDefinition 属性指定数据表对应的 Lob 字段类型
@Column(name = "content", columnDefinition = "LONGTEXT NOT NULL")
private String content;

3.6 创建枚举类型的字段

public enum Gender {
    MALE("男性"),
    FEMALE("女性");

    private String value;
    Gender(String str){
        value=str;
    }
}

可以通过@Enumerated注解去使用枚举类型的字段

@Entity
@Table(name = "role")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    @Enumerated(EnumType.STRING)
    private Gender gender;
    省略getter/setter......
}

数据库里面对应存储的是 MAIL/FEMAIL。

如何验证我们已经成功,运行项目后,查看控制台是否打印出创建表的 sql 语句,并且数据库中表真的被创建出来的话,说明你成功了。

控制台打印出来的 sql 语句类似下面这样:

droptableifexists person
CREATETABLE`person` (
  `id`bigint(20) NOTNULL AUTO_INCREMENT,
  `age`int(11) DEFAULTNULL,
  `name`varchar(255) DEFAULTNULL,
   PRIMARY KEY (`id`)
) ENGINE=InnoDBDEFAULTCHARSET=utf8;
altertable person addconstraint UK_p0wr4vfyr2lyifm8avi67mqw5 unique (name)

4.创建操作数据库的 Repository 接口

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
}

首先这个接口加了 @Repository 注解,代表它和数据库操作有关。另外,它继承了 JpaRepository<Person, Long>接口,而JpaRepository<Person, Long>源码如下:

@NoRepositoryBean
publicinterface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    List<T> findAll();

    List<T> findAll(Sort var1);

    List<T> findAllById(Iterable<ID> var1);

    <S extends T> List<S> saveAll(Iterable<S> var1);

    void flush();

    <S extends T> S saveAndFlush(S var1);

    void deleteInBatch(Iterable<T> var1);

    void deleteAllInBatch();

    T getOne(ID var1);

    <S extends T> List<S> findAll(Example<S> var1);

    <S extends T> List<S> findAll(Example<S> var1, Sort var2);
}

所以当我们继承了JpaRepository<T, ID> ,也就可以去调用 JPA 为我们提供好的增删改查、分页查询以及根据条件查询等方法。

4.1 JPA自带方法用例

4.1.1 增删改查

  1. 增(保存用户到数据库)
    Person person = new Person("SnailClimb", 23);
    personRepository.save(person);
save()方法对应 sql 语句是: insert into person (age, name) values (23,"snailclimb")
  1. 查(根据id查找用户)
 Optional<Person> personOptional = personRepository.findById(id);
findById()方法对应 sql 语句就是:select * from person p where p.id = id
  1. 删(根据id删除用户)
  personRepository.deleteById(id);
deleteById()方法对应 sql 语句就是:delete from person where id=id
  1. 改(根据id更新用户信息)

更新操作也是通过save()方法实现。

 	Person person = new Person("SnailClimb", 23);
    Person savedPerson = personRepository.save(person);
    // 更新 person 对象的姓名
    savedPerson.setName("UpdatedName");
    personRepository.save(savedPerson);

这里是先新增再去更新,也可以先查出一个用户的信息再去更新。

这里最后的save()方法相当于 sql 语句:update person set name="UpdatedName" where id=id

4.1.2 条件查询

下面这些方法是根据 JPA 提供的语法自定义的,你需要将下面这些方法写到 PersonRepository 中。

假如我们想要根据 Name 来查找 Person ,你可以这样:

Optional<Person> findByName(String name);

如果你想要找到年龄大于某个值的人,你可以这样:

List<Person> findByAgeGreaterThan(int age);

4.2 JPA自定义Sql语句

很多时候我们自定义 sql 语句会非常有用。

根据 name 来查找 Person:

@Query("select p from Person p where p.name = :name")
Optional<Person> findByNameCustomeQuery(@Param("name") String name);

Person 部分属性查询,避免 select *操作:

@Query("select p.name from Person p where p.id = :id")
String findPersonNameById(@Param("id") Long id);

根据 id 更新Person name:

@Modifying
@Transactional
@Query("update Person p set p.name = ?1 where p.id = ?2")
void updatePersonNameById(String name, Long id);

根据 id 删除Person name:

@Modifying
@Transactional
@Query("delete from Person where id = ?1")
void deletePersonNameById(Long id);

4.3 创建异步方法

如果我们需要创建异步方法的话,也比较方便。

异步方法在调用时立即返回,然后会被提交给TaskExecutor执行。当然你也可以选择得出结果后才返回给客户端。

@Async
Future<User> findByName(String name);

@Async
CompletableFuture<User> findByName(String name);

5.测试

@SpringBootTest
@RunWith(SpringRunner.class)
publicclass PersonRepositoryTest {
    @Autowired
    private PersonRepository personRepository;
    private Long id;

    /**
     * 保存person到数据库
     */
    @Before
    public void setUp() {
        assertNotNull(personRepository);
        Person person = new Person("SnailClimb", 23);
        Person savedPerson = personRepository.saveAndFlush(person);// 更新 person 对象的姓名
        savedPerson.setName("UpdatedName");
        personRepository.save(savedPerson);

        id = savedPerson.getId();
    }

    /**
     * 使用 JPA 自带的方法查找 person
     */
    @Test
    public void should_get_person() {
        Optional<Person> personOptional = personRepository.findById(id);
        assertTrue(personOptional.isPresent());
        assertEquals("SnailClimb", personOptional.get().getName());
        assertEquals(Integer.valueOf(23), personOptional.get().getAge());

        List<Person> personList = personRepository.findByAgeGreaterThan(18);
        assertEquals(1, personList.size());
        // 清空数据库
        personRepository.deleteAll();
    }

    /**
     * 自定义 query sql 查询语句查找 person
     */

    @Test
    public void should_get_person_use_custom_query() {
        // 查找所有字段
        Optional<Person> personOptional = personRepository.findByNameCustomeQuery("SnailClimb");
        assertTrue(personOptional.isPresent());
        assertEquals(Integer.valueOf(23), personOptional.get().getAge());
        // 查找部分字段
        String personName = personRepository.findPersonNameById(id);
        assertEquals("SnailClimb", personName);
        System.out.println(id);
        // 更新
        personRepository.updatePersonNameById("UpdatedName", id);
        Optional<Person> updatedName = personRepository.findByNameCustomeQuery("UpdatedName");
        assertTrue(updatedName.isPresent());
        // 清空数据库
        personRepository.deleteAll();
    }

}

讲到这里本章对Spring Data Jpa注解和基础操作知识讲解也就结束了,如果想了解更多知识可以在对应的专栏中看系列文章,谢谢大家的观看,希望能给各位同学带来帮助。如果觉得博主写的还可以的,可以点赞收藏。 😉

posted @ 2020-12-08 17:55  奋斗的小宋  阅读(77)  评论(0)    收藏  举报