Java Persistence API(JPA)是Java EE技术的一部分,它为关系型数据提供了一个对象/关系映射解决方案。它允许开发人员使用面向对象的方式操作数据库,而不是使用SQL语言。JPA是Java EE规范的一部分,它是Hibernate等ORM框架的实现。
JPA是一个非常强大的ORM框架,它可以帮助开发人员在Java中使用关系型数据库。它提供了一个高度抽象的API,使得开发人员可以使用面向对象的方式来操作数据库。这种方式比使用SQL语言更加直观和易于理解。JPA还提供了一些高级特性,例如缓存、懒加载和级联操作等,这些特性可以帮助开发人员编写更好的Java应用程序。
JPA很好的一个特性就是用JPA语法规范写的SQL,会根据当前系统使用的数据库类型改变生成的SQL语法,兼容数据库类型的切换,如之前使用的是MySQL,现在换成Oracle,由于不同类型的数据库,SQL语法会有区别,如果使用的是mybatis,就需要手动去改SQL兼容Oracle,而JPA就不用啦,无缝对接.
@Entity注解用于将Java类声明为JPA实体。这意味着该类将映射到数据库中的表。实体类必须具有一个无参构造函数,并且必须具有一个可以唯一标识该实体的@Id属性。
@Table注解用于指定实体将映射到的数据库表的名称。如果未指定,则使用实体的名称。
@Id注解用于指定实体的主键。它可以用于指定单个属性作为主键,也可以用于指定由多个属性组成的复合主键。
@GeneratedValue注解用于指定主键的生成策略。它可以用于自动分配主键值。
@Column注解用于指定实体属性与数据库表中的列的映射。它可以用于指定列的名称,长度,是否允许为空等信息。
@OneToMany注解用于指定实体之间的一对多关系。它可以用于指定实体之间的关系,包括如何在实体之间进行关联和如何在实体之间进行级联操作。
@ManyToOne注解用于指定实体之间的多对一关系。它可以用于指定实体之间的关系,包括如何在实体之间进行关联和如何在实体之间进行级联操作。
@JoinColumn注解用于指定实体之间的关联关系。它可以用于指定用于关联实体的数据库表和列的名称。
@NamedQuery注解用于定义一个命名查询。命名查询是一个在实体管理器上下文中定义的查询,可以在多个实体之间共享。命名查询可以提高代码的可读性和可维护性。
注解 |
作用 |
常用属性 |
@Data |
给实体类加get/set/toString/EqualsAndHashCode方法,是lombok的注解 |
|
@Entity |
指定当前类是实体类 |
|
@Table |
指定实体类和表之间的对应关系 |
name:指定数据库表的名称 |
@EntityListeners |
在实体类增删改的时候监听,为创建人/创建时间等基础字段赋值 |
value:指定监听类 |
@Id |
指定当前字段是主键 |
|
@SequenceGenerator |
指定数据库序列别名 |
sequenceName:数据库序列名name:取的别名 |
@GeneratedValue |
指定主键的生成方式 |
strategy :指定主键生成策略generator:选择主键别名 |
@Column |
指定实体类属性和数据库表之间的对应关系 |
name:指定数据库表的列名称。unique:是否唯一nullable:是否可以为空nserttable:是否可以插入updateable:是否可以更新columnDefinition: 定义建表时创建此列的DDL |
@CreatedBy |
自动插入创建人 |
|
@CreatedDate |
自动插入创建时间 |
|
@LastModifiedBy |
自动修改更新人 |
|
@LastModifiedDate |
自动修改更细时间 |
|
@Version |
自动更新版本号 |
|
@JsonFormat |
插入/修改/读取的时间转换成想要的格式 |
pattern:展示格式timezone:国际时间 |
Spring 数据存储库抽象中的中心接口是 Repository
。它采用要管理的域类以及域类的标识符类型作为类型参数。此接口主要充当标记接口,用于捕获要使用的类型,并帮助您发现扩展此接口的接口。 CrudRepository
和 ListCrudRepository
接口为正在管理的实体类提供复杂的 CRUD 功能。
使用jpa时我们创造的接口在继承Repository时要在泛型中填写对应表的实体类,和主键类型.
public interface CrudRepository extends Repository {
S save(S entity); //1
Optional findById(ID primaryKey); //2
Iterable findAll(); //3
long count(); //4
void delete(T entity); //5
boolean existsById(ID primaryKey); //6
// … more functionality omitted.
}
1 | 保存给定的实体。 |
2 | 返回由给定 ID 标识的实体。 |
3 | 返回所有实体。 |
4 | 返回实体数。 |
5 | 删除给定实体。 |
6 | 指示具有给定 ID 的实体是否存在。 |
此接口中声明的方法通常称为 CRUD 方法。 ListCrudRepository
提供了等效的方法,但它们返回 List
,其中 CrudRepository
方法返回 Iterable
。
还提供了特定于持久性技术的抽象,例如 JpaRepository
或 MongoRepository
。这些接口扩展了 CrudRepository
并公开了底层持久性技术的功能,以及相当通用的持久性技术不可知的接口(如 CrudRepository
)。
除了 CrudRepository
之外,还有一个 PagingAndSortingRepository
抽象,它添加了额外的方法来简化对实体的分页访问:
public interface PagingAndSortingRepository {
Iterable findAll(Sort sort);
Page findAll(Pageable pageable);
}
若要访问页面大小为 20 的 User
的第二页,可以执行以下操作:
PagingAndSortingRepository repository = // … get access to a bean
Page users = repository.findAll(PageRequest.of(1, 20));
注意: jpa分页查询, 默认从第0页算第一页
Spring Data Jpa定义了一些命名规则,可以通过方法名自动生成SQL查询,以下是一些常见的命名规则和对应的where条件:
命名规则 |
对应的where条件 |
findBy + 属性名 |
属性等于给定值 |
findBy + 属性名 + Between |
属性在两个值之间 |
findBy + 属性名 + LessThan |
属性小于给定值 |
findBy + 属性名 + GreaterThan |
属性大于给定值 |
findBy + 属性名 + Like |
属性模糊匹配 |
findBy + 属性名 + In |
属性在给定集合中 |
findBy + 属性名 + Not |
属性不等于给定值 |
findAllBy + 属性名 + OrderBy + 属性名 + Desc/Asc |
根据属性排序 |
除了使用命名规则自动生成SQL查询,还可以使用@Query注解手动编写SQL查询,如下面的例子:
@Repository
public interface UserRepository extends JpaRepository {
@Query("SELECT u FROM User u WHERE u.firstName = :firstName AND u.lastName = :lastName")
List findByFirstNameAndLastName(@Param("firstName") String firstName, @Param("lastName") String lastName);
}
在@Query注解中可以编写自定义的SQL查询语句,使用:参数名来引用方法参数。同时,还可以使用@Param注解来指定方法参数名和SQL语句中的参数名对应关系。
总之,Spring Data Jpa提供了多种查询方法,开发者可以根据实际情况选择最适合的方法进行查询。
下面是一个使用Spring Data JPA完成的连表查询案例:
@Repository
public interface UserRepository extends JpaRepository {
@Query(value = "SELECT u.id, u.name, r.role_name FROM user u LEFT JOIN user_role ur ON u.id = ur.user_id LEFT JOIN role r ON ur.role_id = r.id WHERE u.id = :userId", nativeQuery = true)
List
在这个案例中,我们使用了原生SQL语句来进行连表查询。通过LEFT JOIN将user表、user_role表和role表连接起来,然后使用WHERE条件过滤出指定用户的信息。最后,使用@Query注解将SQL语句与方法关联起来。
在返回值中,我们使用了Object[]类型来存储查询结果。数组中的第一个元素是用户的id,第二个元素是用户的姓名,第三个元素是用户的角色名。
需要注意的是,在使用原生SQL进行查询时,返回值的类型应该与查询结果的数据类型相匹配。否则,程序可能会抛出类型转换异常等错误。
@Query注解使用起来很简单,默认的属性是value,就是当前写的SQL语句,有时会用到nativeQuery属性,这个属性是用来标记当前的SQL是本地SQL,还是符合JPA语法规范的SQL。这里需要解释一下本地SQL和JPA语法规范的SQL区别。
本地SQL,是根据实际使用的数据库类型写的SQL,这种SQL中使用到的一些语法格式不能被JPA解析以及可能不兼容其他数据库,这种SQL称为本地SQL,此时需要将nativeQuery属性设置为true,否则会报错。
JPA语法规范的SQL,往往这种SQL本身是不适用于任何数据库的,需要JPA将这种SQL转换成真正当前数据库所需要的SQL语法格式。
@Query("select u from JpaUser u")
List findQuan();
@Query("from JpaUser")
List findQuan2();
@Query("select u from JpaUser u where u.createdBy=:cb and u.name=:nm")
JpaUser findUser(@Param("cb") String createBy, @Param("nm") String name);
@Query("select u from JpaUser u where u.createdBy=?1 and u.name=?2")
JpaUser findUser2(String createBy, String name);
在SQL上使用占位符的两种方式,第一种是使用":“后加变量的名称,第二种是使用”?“后加方法参数的位置。如果使用”:“的话,需要使用@Param注解来指定变量名;如果使用”?"就需要注意参数的位置。
SQL语句中直接用实体类代表表名,因为在实体类中使用了@Table注解,将该实体类和表进行了关联。
相信在正常的项目开发中都会涉及到修改数据信息的操作,如逻辑删除、封号、解封、修改用户名、头像等等。在使用JPA的时候,如果@Query涉及到update就必须同时加上@Modifying注解,注明当前方法是修改操作。
@Transactional
@Modifying
@Query("update JpaUser u set u.name=:#{#obj.name} where u.createdBy=:#{#obj.createdBy}")
int update2(@Param("obj") JpaUser jpaUser);
@Transactional
@Modifying
@Query(value = "insert into jpa_user(id, name, created_by) values (:#{#obj.id}, :#{#obj.name}, :#{#obj.createdBy})", nativeQuery = true)
int add(@Param("obj") JpaUser jpaUser);
经常使用的查询方式:
关键字 |
方法命名 |
sql where字句 |
And |
findByNameAndPwd |
where name = ? and pwd= ? |
Or |
findByNameOrSex |
where name = ? or sex =? |
Between |
findByIdBetween |
where id between ? and ? |
LessThan |
findByIdLessThan |
where id |
LessThanEqual |
findByIdLessThanEqual |
where id <=? |
GreaterThan |
findByIdGreaterThan |
where id > ? |
GreaterThanEqual |
findByIdGreaterThanEqual |
where id >= ? |
After |
findByIdAfter |
where id > ? |
Before |
find ByIdBefore |
where id < ? |
IsNull |
findByNameIsNull |
where name is null |
IsNotNull,NotNull |
findByNameIsNotNullfindByNameNotNull |
where name is not null |
Like |
findByNameLike |
where name like ? |
NotLike |
findByNameNotLike |
where name not like ? |
StartingWith |
findByNameStartingWith |
where name like ‘?%’ |
EndingWith |
findByNameEndingWith |
where name like ‘%?’ |
Containing |
findByNameContaining |
where name like ‘%?%’ |
OrderBy |
findByIdOrderByAgeDescAndIdAsc |
where id = ? order by age desc,id asc |
Not |
findByNameNot |
where name <> ? |
In |
findByNameIn |
where name in (?) |
NotIn |
findByIdNotIn |
where id not in (?) |
True |
findByDelStatusTrue |
where delStatus = true |
False |
findByDelStatusFalse |
where delStatus = false |
IgnoreCase |
findByNameIgnoreCase |
where UPPER(name) = UPPER(?) |
举例说明:
select *
from t_user
where
user_name like '%er'
and id >=100
and (age between 20 and 35)
order by age DESC,id ASC
List findByUserNameLikeAndIdGreaterThanEqualAndAgeBetweenOrderByAgeDescAndIdAsc(String userName,Long id,Integer startAge,Integer endAge);
说明:userName、id分别匹配前面两个参数,由于age是between范围查询,是在两个值之间,需要对应到两个参数。所以做种的参数是四个,且位置要按照顺序传入,不能错位.
模糊查询
List findByUserNameLike(String userName); //1
List findByUserNameNotLike(String userName); //2
List findByUserNameStartingWith(String userName);//3
List findByUserNameEndingWith(String userName); //4
List findByUserNameContaining(String userName); //5
1:表示按照userName字段模糊查询,会自动加上like关键字,但是不会在userName对应的值上面添加%或者_,需要在传入值的时候,手动将占位符和实际值组装起来;
2:会自动加上not like关键字,占位符需要自己手动添加;
3:自动加上like关键字,这里不需要手动添加占位符,会自动在字段值的前面加上%,转换后:like '%Joker';
4:和第三句差不多,差别就是占位符%放置的位置调整,放在了字段值的后面;
5:相当于第三句和第四句的综合,会在字段值的前后加上占位符%。
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
在@GeneratedValue注解中我们只需要生成策略为IDENTITY,即可完成mysql数据库的主键自增长。
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.TABLE, generator = "sequence_table")
@TableGenerator(name = "sequence_table",
allocationSize = 1,
table = "sequence_table",
pkColumnName = "sequence_name",
valueColumnName = "sequence_count")
private Long id;
spring:
jpa:
hibernate:
ddl-auto: update #自动更新
show-sql: true #日志中显示sql语句
ddl-auto: create:启动时删除上一次生成的表,并根据实体类生成表,表中数据会被清空
ddl-auto: create-drop:启动时根据实体类生成表,程序关闭时表会被删除
ddl-auto: update:启动时会根据实体类生成表,当实体类属性变动的时候,表结构也会更新,在初期开发阶段使用此选项
ddl-auto: validate:启动时验证实体类和数据表是否一致,在数据结构稳定时采用此选项
ddl-auto: none:不采取任何措施
建议只使用update和none,前者适合初期建表,后者适合建表完成后保护表结构
show-sql: true,这个属性代表是否开启显示sql语句,为true我们就可以在每一次对数据库的操作在控制台看到所使用的sql语句了,方便找错,很方便的属性,建议开发时开启,上线后关闭
ddl-auto: update时,jpa会根据实体类帮助我们创建表~
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
1.主键生成策略分为四种:SEQUENCE策略、IDENTITY策略、TABLE策略、AUTO策略;
2.SEQUENCE策略适合拥有序列的数据库,比如Oracle;
3.IDENTITY策略适合拥有主键自增长的数据库,比如Mysql;
4.TABLE策略是通过一张序列表来维护主键插入的值的,所以适合所有数据库;
5.AUTO策略是jpa自行判断使用上面三个中的哪一个作为主键生成策略;
6.推荐使用SEQUENCE和IDENTITY策略,开发人员应该自行判断使用的是何种数据库,而不是由jpa进行判断。
- @Version:版本号;进行update操作时启动乐观锁,@Version修饰的字段值与数据库中字段值一致才能进行修改
- @CreatedDate :创建时间;进行insert操作时,将当前时间插入到@CreatedDate修饰字段中;进行update操作时,会随实体类中的@CreatedDate修饰的字段值进行修改
- @CreatedBy:创建人;进行insert操作时,将当前用户名插入到@CreatedBy修饰字段中;进行update操作时,会随实体类中的@CreatedBy修饰的字段值进行修改
- @LastModifiedDate:最后一次修改时间;进行update操作时,将当前时间修改进@LastModifiedDate修饰字段中;进行insert操作时,将当前时间插入到@LastModifiedDate修饰字段中
- @LastModifiedBy :最后一次修改的修改人;进行update操作时,将当前修改人修改进@LastModifiedBy修饰的字段中;进行insert操作时,将当前用户名插入到@LastModifiedBy修饰字段中
1.1在Springboot启动类上加上启动审计注解:@EnableJpaAuditing
@EnableJpaAuditing
@SpringBootApplication
public class SpringContextApplication {
public static void main(String[] args) {
SpringApplication.run(SpringContextApplication.class, args);
}
}
1.2在实体类上方加上监听注解:@EntityListeners(AuditingEntityListener.class)
@Data
@Entity
@Table(name = "JPA_USER")
@EntityListeners(AuditingEntityListener.class)
public class JpaUser {
//...
}
当设置完这两个注解后@CreatedDate、@LastModifiedBy这两个注解对于创建时间和修改时间的注入就ok了,但是对创建人、修改人的注入却为null,毕竟jpa并不知道当前是谁在操作数据,需要我们来进行提供.
2.1建立配置文件夹config,创建配置类UserAuditor
import com.itszt22.springdatajpa.entity.JpaUser;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Optional;
//将来JpaUser入库的时候, Jpa会自动调用getCurrentAuditor方法, 将返回的结果, 自动插入到对象的创建人和更新人字段中
@Configuration
public class UserAuditor implements AuditorAware {
/**
* - *获取当前创建或修改的用户*
* - **
* - *@return 获取当前创建或修改的用户Uid*
* -
*/
@Override
public Optional getCurrentAuditor() {
HttpServletRequest request;
// 工号
String username = "anonymous";
// 拿到Session
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
request = requestAttributes.getRequest();
HttpSession session = request.getSession();
Object obj = session.getAttribute("jpaUser");
if (obj instanceof JpaUser) {
JpaUser jpaUser = (JpaUser) obj;
username = jpaUser.getName();
}
username = request.getParameter("username");
}
return Optional.of(username);
}
}
很容易理解的一个配置,它返回一个Optional
关于Optional>,不了解的同学可以去度娘,java8很好的一个防止空指针的类
session大家应该耳熟能详,但一般我们是在controller层拿到request,再拿session,或者直接在controller层拿到session,在其他非controller类中如何拿session呢,使用RequestContextHolder上下文容器
具体解析一下代码:
- 声明一个HttpServletRequest对象request,声明一个username字符串,默认值为anonymous(匿名)
- 通过RequestContextHolder拿到request的属性,转换成可以得到request对象的ServletRequestAttributes
- 判断是否当前线程的请求属性为空,为空则直接返回username默认值,不为空继续
- 不为空就可以拿到我们熟悉的request对象啦,它里面存着session信息
- 用Object类接收约定好的key值(我这里叫jpaUser),如果存储了session,那么拿到的对象类型一定是JpaUser,进行强转后拿到存储在其中的name值
- 再返回拥有真实用户名的username,接下来就交给jpa审计去注入了
org.springframework.boot
spring-boot-starter-data-jpa
com.mysql
mysql-connector-j
runtime
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
spring:
datasource:
# url配置有一个自动建库的配置, 很重要的前提 mysql安装的时候已经配置了utf8mb4默认字符集
url: jdbc:mysql://这里填自己的ip地址:3306/jpa?createDatabaseIfNotExist=true&useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: xxx
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update #自动更新
show-sql: true #日志中显示sql语句
注意: 这个jpa启动的时候会自动建表, 先检查有没有表名称, 如果有, 则不创建, 否则建表; 如果实体类结构发生修改, 也会自动修改对应的表结构.
除了@SpringBootApplication启动注解外,还有一个注解@EnableJpaAuditing,它是用来启动Jpa的审计功能,比如说在使用建表中经常会加入 版本号、创建时间、修改时间 、创建者、修改者 这五个字段。因此为了简化开发, 我们可以将其交给jpa来自动填充。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@EnableJpaAuditing
@SpringBootApplication
public class DemojpaApplication {
public static void main(String[] args) {
SpringApplication.run(DemojpaApplication.class, args);
}
}
实体类
@Data
@Entity
@Table(name = "JPA_USER")
@EntityListeners(AuditingEntityListener.class)
public class JpaUser {
@Id
@Column(name = "ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "NAME")
private String name;
@Column(name = "OBJECT_VERSION" )
@Version
private Long objectVersion;
@Column(name = "CREATED_BY")
@CreatedBy
private String createdBy;
@Column(name = "CREATED_DATE")
@CreatedDate
private Date createdDate;
@Column(name = "LAST_UPDATED_BY" )
@LastModifiedBy
private String lastUpdatedBy;
@Column(name = "LAST_UPDATED_DATE" )
@LastModifiedDate
private Date lastUpdatedDate;
}
public interface JpaUserRepository extends JpaRepository {
}
@RestController
@RequestMapping(value = "/jpa")
public class JpaTestController {
@Autowired
private JpaUserRepository jpaUserRepository;
@PostMapping(value = "/add")
public JpaUser addUser(@RequestBody JpaUser user) {
return jpaUserRepository.save(user);
}
}
@RestController
@RequestMapping(value = "/jpa")
public class JpaTestController {
@Autowired
private JpaUserRepository jpaUserRepository;
@DeleteMapping(value = "/delete/{id}")
public void deleteUser(@PathVariable("id") Long id) {
jpaUserRepository.deleteById(id);
}
}
@RestController
@RequestMapping(value = "/jpa")
public class JpaTestController {
@Autowired
private JpaUserRepository jpaUserRepository;
@PutMapping(value = "/update")
public JpaUser updateUser(@RequestBody JpaUser user) {
return jpaUserRepository.save(user);
}
}
@RestController
@RequestMapping(value = "/jpa")
public class JpaTestController {
@Autowired
private JpaUserRepository jpaUserRepository;
@GetMapping(value = "/get/{id}")
public Optional getUserById(@PathVariable("id") Long id) {
return jpaUserRepository.findById(id);
}
@GetMapping(value = "/get_all")
public List getAllUsers() {
return jpaUserRepository.findAll();
}
}
最后安利大家看一个very wonderful 的电视剧,那就是少年歌行,特别好看...(♥∀♥)