桃花岛--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种:
- create:每次重新启动项目都会重新创建新表结构,会导致数据丢失
- create-drop:每次启动项目创建表结构,关闭项目删除表结构
- update:每次启动项目会更新表结构
- 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 增删改查
- 增(保存用户到数据库)
Person person = new Person("SnailClimb", 23);
personRepository.save(person);
save()方法对应 sql 语句是: insert into person (age, name) values (23,"snailclimb")
- 查(根据id查找用户)
Optional<Person> personOptional = personRepository.findById(id);
findById()方法对应 sql 语句就是:select * from person p where p.id = id
- 删(根据id删除用户)
personRepository.deleteById(id);
deleteById()方法对应 sql 语句就是:delete from person where id=id
- 改(根据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注解和基础操作知识讲解也就结束了,如果想了解更多知识可以在对应的专栏中看系列文章,谢谢大家的观看,希望能给各位同学带来帮助。如果觉得博主写的还可以的,可以点赞收藏。 😉

浙公网安备 33010602011771号