JPA作持久层操作
JPA(Hibernate是jpa的实现)
jpa是对实体类操作,从而通过封装好的接口直接设置数据库的表结构。虽然jpa可以直接通过编写java代码来操作数据库表结构,避免了sql的编写,但别忘了需要先建立jpa需要操作的数据库并更改配置文件到该数据库,jpa不能建库!!!
国外比较流行jpa,国内更加流行mybatis,因为mybatis直接操作数据库容易懂和后期维护一点。(其实是国内程序员乱搞,国外的比较有规矩)
本文只介绍了jpa的基本使用操作以及基本语法
JPA VS Mybatis
大项目用mybatis,小项目(微服务:小程序等)用JPA (JPA方便,但大项目到后期需要从sql语句上优化时JPA无法优化)
JPA操作
jpa是javax包下的,所以后面导包的时候注意一下,别导错了。只有事务、Param的导包是导spring框架下的
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
设置配置yaml
spring:
jpa:
#开启SQL语句执行日志信息
show-sql: true //生产环境下就改成false吧,true没必要
hibernate:
#配置为自动创建
ddl-auto: update
创建实体类
@Data
@Entity //表示这个类是一个实体类 javax包下的
@Table(name = "users") //对应的数据库中表名称 javax包下的
public class Account {
@GeneratedValue(strategy = GenerationType.IDENTITY) //生成策略,这里配置为自增
@Column(name = "id") //对应表中id这一列
@Id //此属性为主键
int id;
@Column(name = "username") //对应表中username这一列
String username;
@Column(name = "password") //对应表中password这一列
String password;
}
创建repo包,建Repository类
- 每一个表都要设置相应的Repository实现类,service层可以通过该类对象操作数据库(因为该类封装了操作数据库的方法集)
- 如果需要的操作没有被封装,还可以在Repository类中用已经封装的方法自定义新的方法(自定义规则在下面)
注:JpaRepository有两个泛型,前者是具体操作的对象实体,也就是对应的表,后者是ID的类型
@Repository //别忘了注入bean
public interface AccountRepository extends JpaRepository<Account, Integer> { //<A,B>A中是表的实体类,B是ID的类型
}
调用方法执行sql
@Resource
AccountRepository accountRepository;
void pageAccountService() {
accountRepository.findById(1).ifPresent(System.out::println);
}
补充:
在Repository中根据方法名称拼接自定义sql
例子:
repository:
@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {
Account findByIdAndUsername(int id, String username);
//可以使用Optional类进行包装,Optional<Account> findByIdAndUsername(int id, String username);
//方法名称拼接自定义模糊查询
List<Account> findAllByUsernameLike(String str);
}
service:
@Resource
AccountRepository accountRepository;
void pageAccountService(String str) {
accountRepository.findAllByUsernameLike("%str%").forEach(System.out::println); //不建议这种模糊查询,建议用原生sql的concat,以免sql注入
}
方法拼接规则:
虽然接口预置的方法使用起来非常方便,但是如果我们需要进行条件查询等操作或是一些判断,就需要自定义一些方法来实现,同样的,我们不需要编写SQL语句,而是通过方法名称的拼接来实现条件判断,这里列出了所有支持的条件判断名称:
Distinct |
findDistinctByLastnameAndFirstname |
select distinct … where x.lastname = ?1 and x.firstname = ?2 |
---|---|---|
And |
findByLastnameAndFirstname |
… where x.lastname = ?1 and x.firstname = ?2 |
Or |
findByLastnameOrFirstname |
… where x.lastname = ?1 or x.firstname = ?2 |
Is ,Equals |
findByFirstname ,findByFirstnameIs ,findByFirstnameEquals |
… where x.firstname = ?1 |
Between |
findByStartDateBetween |
… where x.startDate between ?1 and ?2 |
LessThan |
findByAgeLessThan |
… where x.age < ?1 |
LessThanEqual |
findByAgeLessThanEqual |
… where x.age <= ?1 |
GreaterThan |
findByAgeGreaterThan |
… where x.age > ?1 |
GreaterThanEqual |
findByAgeGreaterThanEqual |
… where x.age >= ?1 |
After |
findByStartDateAfter |
… where x.startDate > ?1 |
Before |
findByStartDateBefore |
… where x.startDate < ?1 |
IsNull ,Null |
findByAge(Is)Null |
… where x.age is null |
IsNotNull ,NotNull |
findByAge(Is)NotNull |
… where x.age not null |
Like |
findByFirstnameLike |
… where x.firstname like ?1 |
NotLike |
findByFirstnameNotLike |
… where x.firstname not like ?1 |
StartingWith |
findByFirstnameStartingWith |
… where x.firstname like ?1 (参数与附加% 绑定) |
EndingWith |
findByFirstnameEndingWith |
… where x.firstname like ?1 (参数与前缀% 绑定) |
Containing |
findByFirstnameContaining |
… where x.firstname like ?1 (参数绑定以% 包装) |
OrderBy |
findByAgeOrderByLastnameDesc |
… where x.age = ?1 order by x.lastname desc |
Not |
findByLastnameNot |
… where x.lastname <> ?1 |
In |
findByAgeIn(Collection<Age> ages) |
… where x.age in ?1 |
NotIn |
findByAgeNotIn(Collection<Age> ages) |
… where x.age not in ?1 |
True |
findByActiveTrue() |
… where x.active = true |
False |
findByActiveFalse() |
… where x.active = false |
IgnoreCase |
findByFirstnameIgnoreCase |
… where UPPER(x.firstname) = UPPER(?1) |
完全自定义sql
JPQL操作实体类,从而操作数据库
比如我们要更新用户表中指定ID用户的密码:
@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {
@Transactional //DML操作需要事务环境,可以不在这里声明在方法前声明,但是调用时一定要处于事务环境下
@Modifying //表示这是一个DML操作
@Query("update Account set password = ?2 where id = ?1") //这里操作的是一个实体类对应的表,参数使用?代表,后面接第n个参数
int updatePasswordById(int id, String newPassword);
//@Query("update Account set password = :password where username = :name") 这样写也可以
int updatePasswordByUsername(@Param("name") String username, //我们可以使用@Param指定名称
@Param("password") String newPassword);
}
@Test
void updateAccount(){
repository.updatePasswordById(1, "654321");
}
原生SQL直接操作数据库
实现根据用户名称修改密码:
@Transactional
@Modifying
@Query(value = "update users set password = :password where username = :name", nativeQuery = true) //使用原生SQL,除了占位符不同其他和Mybatis一样,这里使用 :名称 表示参数,当然也可以继续用上面那种方式。
int updatePasswordByUsername(@Param("name") String username, //我们可以使用@Param指定名称
@Param("password") String newPassword);
@Test
void updateAccount(){
repository.updatePasswordByUsername("Admin", "654321");
}
JPA关联查询
对一:会在写了注解(@OneToOne @MangToOne)的类中创建字段。对多:需要多的那方原本就有字段,才可以用该字段对应注解这方的主键
一对一
而用户信息和用户详细信息之间形成了一对一的关系,那么这时我们就可以直接在类中指定这种关系:
@Data
@Entity
@Table(name = "users")
public class Account {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@Id
int id;
@Column(name = "username")
String username;
@Column(name = "password")
String password;
@JoinColumn(name = "detail_id") //指定存储外键的字段名称。在本表中创建detail_id,并外键连接AccountDetail表的主键id
@OneToOne //声明为一对一关系
AccountDetail detail;
//执行代码:alter table users add constraint xxxxx foreign key (detail_id) references users_detail (id)
}
在修改实体类信息后,我们发现在启动时也进行了更新,日志如下:
Hibernate: alter table users add column detail_id integer
Hibernate: create table users_detail (id integer not null auto_increment, address varchar(255), email varchar(255), phone varchar(255), real_name varchar(255), primary key (id)) engine=InnoDB
Hibernate: alter table users add constraint FK7gb021edkxf3mdv5bs75ni6jd foreign key (detail_id) references users_detail (id)
测试查询:
@Test
void pageAccount() {
repository.findById(1).ifPresent(System.out::println);
}
结果:在建立关系之后,我们查询Account对象时,会自动将关联数据的结果也一并进行查询。
Hibernate: select account0_.id as id1_0_0_, account0_.detail_id as detail_i4_0_0_, account0_.password as password2_0_0_, account0_.username as username3_0_0_, accountdet1_.id as id1_1_1_, accountdet1_.address as address2_1_1_, accountdet1_.email as email3_1_1_, accountdet1_.phone as phone4_1_1_, accountdet1_.real_name as real_nam5_1_1_ from users account0_ left outer join users_detail accountdet1_ on account0_.detail_id=accountdet1_.id where account0_.id=?
Account(id=1, username=Test, password=123456, detail=AccountDetail(id=1, address=四川省成都市青羊区, email=8371289@qq.com, phone=1234567890, realName=本伟))
懒加载
不想加载外键表的信息时,可以设置懒加载,这样只有在需要时才会向数据库获取:
设置懒加载后,使用懒加载设置过的属性时的方法需要在事务环境下获取(因为repository方法调用完后Session会立即关闭
@JoinColumn(name = "detail_id")
@OneToOne(fetch = FetchType.LAZY) //将获取类型改为LAZY
AccountDetail detail;
接着我们测试一下:(测试类里开启事务会自动回滚,不想回滚则在方法前加@Commit。当然毕竟是测试类还是建议保留自动回滚)
@Transactional //懒加载属性需要在事务环境下获取,因为repository方法调用完后Session会立即关闭
@Test
void pageAccount() {
repository.findById(1).ifPresent(account -> {
System.out.println(account.getUsername()); //获取用户名
System.out.println(account.getDetail()); //获取详细信息(懒加载)
});
}
接着我们来看看控制台输出了什么:可以看到,获取用户名之前,并没有去查询用户的详细信息,而是当我们获取详细信息时才进行查询并返回AccountDetail对象。
Hibernate: select account0_.id as id1_0_0_, account0_.detail_id as detail_i4_0_0_, account0_.password as password2_0_0_, account0_.username as username3_0_0_ from users account0_ where account0_.id=?
Test
Hibernate: select accountdet0_.id as id1_1_0_, accountdet0_.address as address2_1_0_, accountdet0_.email as email3_1_0_, accountdet0_.phone as phone4_1_0_, accountdet0_.real_name as real_nam5_1_0_ from users_detail accountdet0_ where accountdet0_.id=?
AccountDetail(id=1, address=四川省成都市青羊区, email=8371289@qq.com, phone=1234567890, realName=卢本)
关联表之间操作一致
【提前更改实体类的设定】
@JoinColumn(name = "detail_id")
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) //设置关联操作为ALL
AccountDetail detail;
- ALL:所有操作都进行关联操作
- PERSIST:插入操作时才进行关联操作
- REMOVE:删除操作时才进行关联操作
- MERGE:修改操作时才进行关联操作
可以多个并存,接着我们来进行一下测试:
@Test
void addAccount(){
Account account = new Account();
account.setUsername("Nike");
account.setPassword("123456");
AccountDetail detail = new AccountDetail();
detail.setAddress("重庆市渝中区解放碑");
detail.setPhone("1234567890");
detail.setEmail("73281937@qq.com");
detail.setRealName("张三");
account.setDetail(detail);
account = repository.save(account); //记得用repository保存
System.out.println("插入时,自动生成的主键ID为:"+account.getId()+",外键ID为:"+account.getDetail().getId());
}
可以看到日志结果:结束后会发现数据库中两张表都同时存在数据。
Hibernate: insert into users_detail (address, email, phone, real_name) values (?, ?, ?, ?)
Hibernate: insert into users (detail_id, password, username) values (?, ?, ?)
插入时,自动生成的主键ID为:6,外键ID为:3
一对多
接着我们来看一对多关联,比如每个用户的成绩信息:
Account类:
@JoinColumn(name = "uid") //注意这里的name指的是Score表中的uid字段对应的就是当前的主键,会将uid外键设置为当前的主键
//执行的语句为:alter table account_score add constraint xxxxx foreign key (uid) reference account(id)
@OneToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL) //在移除Account时,一并移除所有的成绩信息,依然使用懒加载
List<Score> scoreList;
@Data
@Entity
@Table(name = "users_score") //成绩表,注意只存成绩,不存学科信息,学科信息id做外键
public class Score {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@Id
int id;
@OneToOne //一对一对应到学科上
@JoinColumn(name = "cid")
Subject subject;
@Column(name = "socre")
double score;
@Column(name = "uid")
int uid;
}
@Data
@Entity
@Table(name = "subjects") //学科信息表
public class Subject {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cid")
@Id
int cid;
@Column(name = "name")
String name;
@Column(name = "teacher")
String teacher;
@Column(name = "time")
int time;
}
在数据库中填写相应数据,接着我们就可以查询用户的成绩信息了:成功得到用户所有的成绩信息,包括得分和学科信息。
@Transactional
@Test
void test() {
repository.findById(1).ifPresent(account -> {
account.getScoreList().forEach(System.out::println);
});
}
多对一
同样的,我们还可以将对应成绩中的教师信息单独分出一张表存储,并建立多对一的关系,因为多门课程可能由同一个老师教授:
Subjects表:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "tid") //存储教师ID的字段,和一对一是一样的,也会在当前表中创个外键tid,对应Teacher表的主键
Teacher teacher;
//执行的代码:
//alter table subjects add colunm tid integer
//alter table subjects add constraint xxxxx foreign key(tid) reference teachers (id)
接着就是教师实体类了:
@Data
@Entity
@Table(name = "teachers")
public class Teacher {
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
int id;
@Column(name = "name")
String name;
@Column(name = "sex")
String sex;
}
最后我们再进行一下测试:
@Transactional
@Test
void test() {
repository.findById(3).ifPresent(account -> {
account.getScoreList().forEach(score -> {
System.out.println("课程名称:"+score.getSubject().getName());
System.out.println("得分:"+score.getScore());
System.out.println("任课教师:"+score.getSubject().getTeacher().getName());
});
});
}
成功得到多对一的教师信息。
多对多
最后我们再来看最复杂的情况,现在我们一门课程可以由多个老师教授,而一个老师也可以教授多个课程,那么这种情况就是很明显的多对多场景,现在又该如何定义呢?我们可以像之前一样,插入一张中间表表示教授关系,这个表中专门存储哪个老师教哪个科目:
Subjects表:
@ManyToMany(fetch = FetchType.LAZY) //多对多场景
//注意此操作,最后只会在该表多一个字段,和一个中间表,不会在另一个表中多一个字段。需要编写类似代码才会出现
@JoinTable(name = "teach_relation", //多对多中间关联表
joinColumns = @JoinColumn(name = "cid"), //当前实体主键在关联表中的字段名称
inverseJoinColumns = @JoinColumn(name = "tid") //教师实体主键在关联表中的字段名称,并在当前表中创建tid字段作为外键连接关联表的tid
)
List<Teacher> teacher;
接着,JPA会自动创建一张中间表,并自动设置外键,我们就可以将多对多关联信息编写在其中了。
JPA作持久层操作的更多相关文章
- Hibernate深入浅出(九)持久层操作——数据保存&批量操作
数据保存: 1)session.save session.save方法用于实体对象到数据库的持久化操作.也就是说,session.save方法调用与实体对象所匹配的Insert SQL,将数据插入 ...
- mybatis与hibernate的区别持久层对比【面试题】
Mybatis技术特点: 好处: 通过直接编写SQL语句,可以直接对SQL进行性能的优化: 学习门槛低,学习成本低.只要有SQL基础,就可以学习mybatis,而且很容易上手: 由于直接编写SQL语句 ...
- SpringBoot2.0 基础案例(09):集成JPA持久层框架,简化数据库操作
一.JAP框架简介 JPA(Java Persistence API)意即Java持久化API,是Sun官方在JDK5.0后提出的Java持久化规范.主要是为了简化持久层开发以及整合ORM技术,结束H ...
- 八:SpringBoot-集成JPA持久层框架,简化数据库操作
SpringBoot-集成JPA持久层框架,简化数据库操作 1.JPA框架简介 1.1 JPA与Hibernate的关系: 2.SpringBoot整合JPA Spring Data JPA概述: S ...
- JPA规范及其它持久层框架
JPA是一种规范,而hibernate是JPA的一种实现 JPA全称为Java Persistence API ,Java持久化API是Sun公司在Java EE 5规范中提出的Java持久化接口.J ...
- 数据持久层框架iBatis, Hibernate 与 JPA 比较
在本文中我们介绍并比较两种最流行的开源持久框架:iBATIS和Hibernate,我们还会讨论到Java Persistence API(JPA).我们介绍每种解决方案并讨论其所规定的品质,以及在广泛 ...
- 持久层框架JPA与Mybatis该如何选型
一.现状描述 目前java 持久层ORM框架应用最广泛的就是JPA和Mybatis.JPA只是一个ORM框架的规范, 对该规范的实现比较完整就是Spring Data JPA(底层基于Hibernat ...
- SpringBoot整合持久层技术--(三)Spring Data JPA
简介: JPA(java Persistence API)和SpringData是两个范畴的概念.spring data jpa是spring公司下的spring data项目的一个模块. sprin ...
- 【持久层框架】- SpringData - JPA
SpringData - JPA 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! JPA简 ...
随机推荐
- SpringBoot线程池
1.遇到的场景 提高一下插入表的性能优化,两张表,先插旧的表,紧接着插新的表,若是一万多条数据就有点慢了 2.使用步骤 用Spring提供的对ThreadPoolExecutor封装的线程池Threa ...
- Python模块Ⅱ
Python模块2 part3 模块的分类: 内置模块200种左右:python自带的模块,time os sys hashlib等 第三方模块6000种左右:需要pip install beauti ...
- 8条github使用小技巧
1 简介 作为全球最大的开源及私有软件项目托管社区平台,github可以显著地帮助从事编程相关工作的人员提升自己的技术水平,也是费老师我日常最常浏览学习的技术类网站. github为了使得其使用更加便 ...
- Bean Validator
Bean Validator 关于Jakarta EE 2018年03月, Oracle 决定把 JavaEE 移交给开源组织 Eclipse 基金会,并且不再使用Java EE这个名称. 因此jav ...
- Jenkins之配置GitHub-Webhook
前提条件1: 运行Jenkins的机器需要安装git,并且git.exe文件在系统的环境变量里面,或者手动在 Manage Jenkins -> Global Tool Configuratio ...
- VMware 虚拟机安装CentOS镜像详细步骤
CentOS目前官网提供的下载版本有6.7.8,最新的版本为8,不过个人推荐CentOS 7 的版本,因为相比较于最新版本,版本7更加地稳定.而相比于版本6,版本7新增了很多的功能.CentOS 7 ...
- Linux命令格式、终端类型和获取帮助的方法
Linux用户类型 Root用户:超级管理员,权限很大 普通用户:权限有限 终端 terminal 终端类型 物理终端:鼠标.键盘.显示器 虚拟终端:软件模拟出来的终端 控制台终端: /dev/con ...
- C4C UI Design背景色
- lerna学习笔记
lerna简介 Lerna 是一个优化基于Git+npm的多package项目的项目管理工具,lerna是架构优化的产物,项目复杂度提升后,架构优化的主要目标是以提高ROI为核心的 lerna的主要功 ...
- Eclipse拷贝动态的web工程
1.选中需要拷贝的工程,CTRL+C,然后CTRL+V 2.在web动态工程中,还需要选中新拷贝工程,右键选中properties,然后搜索web,--->Web Project Setttin ...