ToB企服应用市场:ToB评测及商务社交产业平台

标题: JPA作持久层操作 [打印本页]

作者: 勿忘初心做自己    时间: 2022-9-16 17:11
标题: 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框架下的
导入依赖
  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-data-jpa</artifactId>
  4. </dependency>
复制代码
设置配置yaml
  1. spring:
  2.   jpa:
  3.                 #开启SQL语句执行日志信息
  4.     show-sql: true        //生产环境下就改成false吧,true没必要
  5.     hibernate:
  6.             #配置为自动创建
  7.       ddl-auto: update
复制代码
创建实体类
  1. @Data
  2. @Entity   //表示这个类是一个实体类 javax包下的
  3. @Table(name = "users")    //对应的数据库中表名称 javax包下的
  4. public class Account {
  5.     @GeneratedValue(strategy = GenerationType.IDENTITY)   //生成策略,这里配置为自增
  6.     @Column(name = "id")    //对应表中id这一列
  7.     @Id     //此属性为主键
  8.     int id;
  9.     @Column(name = "username")   //对应表中username这一列
  10.     String username;
  11.     @Column(name = "password")   //对应表中password这一列
  12.     String password;
  13. }
复制代码
创建repo包,建Repository类

注:JpaRepository有两个泛型,前者是具体操作的对象实体,也就是对应的表,后者是ID的类型
  1. @Repository        //别忘了注入bean
  2. public interface AccountRepository extends JpaRepository<Account, Integer> {        //<A,B>A中是表的实体类,B是ID的类型
  3.    
  4. }
复制代码
调用方法执行sql
  1. @Resource
  2. AccountRepository accountRepository;
  3. void pageAccountService() {
  4.     accountRepository.findById(1).ifPresent(System.out::println);
  5. }
复制代码
补充:

在Repository中根据方法名称拼接自定义sql

例子:
repository:
  1. @Repository
  2. public interface AccountRepository extends JpaRepository<Account, Integer> {
  3.     Account findByIdAndUsername(int id, String username);
  4.   //可以使用Optional类进行包装,Optional<Account> findByIdAndUsername(int id, String username);
  5.    
  6.     //方法名称拼接自定义模糊查询
  7.     List<Account> findAllByUsernameLike(String str);
  8. }
复制代码
service:
  1. @Resource
  2. AccountRepository accountRepository;
  3. void pageAccountService(String str) {
  4.      accountRepository.findAllByUsernameLike("%str%").forEach(System.out::println); //不建议这种模糊查询,建议用原生sql的concat,以免sql注入
  5. }
复制代码
方法拼接规则:

虽然接口预置的方法使用起来非常方便,但是如果我们需要进行条件查询等操作或是一些判断,就需要自定义一些方法来实现,同样的,我们不需要编写SQL语句,而是通过方法名称的拼接来实现条件判断,这里列出了所有支持的条件判断名称:
DistinctfindDistinctByLastnameAndFirstnameselect distinct … where x.lastname = ?1 and x.firstname = ?2AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2Is,EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2LessThanfindByAgeLessThan… where x.age < ?1LessThanEqualfindByAgeLessThanEqual… where x.age  ?1GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1AfterfindByStartDateAfter… where x.startDate > ?1BeforefindByStartDateBefore… where x.startDate < ?1IsNull,NullfindByAge(Is)Null… where x.age is nullIsNotNull,NotNullfindByAge(Is)NotNull… where x.age not nullLikefindByFirstnameLike… where x.firstname like ?1NotLikefindByFirstnameNotLike… where x.firstname not like ?1StartingWithfindByFirstnameStartingWith… where x.firstname like ?1(参数与附加%绑定)EndingWithfindByFirstnameEndingWith… where x.firstname like ?1(参数与前缀%绑定)ContainingfindByFirstnameContaining… where x.firstname like ?1(参数绑定以%包装)OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname descNotfindByLastnameNot… where x.lastname  ?1InfindByAgeIn(Collection ages)… where x.age in ?1NotInfindByAgeNotIn(Collection ages)… where x.age not in ?1TruefindByActiveTrue()… where x.active = trueFalsefindByActiveFalse()… where x.active = falseIgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstname) = UPPER(?1)完全自定义sql

JPQL操作实体类,从而操作数据库

比如我们要更新用户表中指定ID用户的密码:
  1. @Repository
  2. public interface AccountRepository extends JpaRepository<Account, Integer> {
  3.     @Transactional    //DML操作需要事务环境,可以不在这里声明在方法前声明,但是调用时一定要处于事务环境下
  4.     @Modifying     //表示这是一个DML操作
  5.     @Query("update Account set password = ?2 where id = ?1") //这里操作的是一个实体类对应的表,参数使用?代表,后面接第n个参数
  6.     int updatePasswordById(int id, String newPassword);
  7.    
  8.     //@Query("update Account set password = :password where username = :name")  这样写也可以
  9.     int updatePasswordByUsername(@Param("name") String username,   //我们可以使用@Param指定名称
  10.                              @Param("password") String newPassword);
  11. }
复制代码
  1. @Test
  2. void updateAccount(){
  3.     repository.updatePasswordById(1, "654321");
  4. }
复制代码
原生SQL直接操作数据库

实现根据用户名称修改密码:
  1. @Transactional
  2. @Modifying
  3. @Query(value = "update users set password = :password where username = :name", nativeQuery = true) //使用原生SQL,除了占位符不同其他和Mybatis一样,这里使用 :名称 表示参数,当然也可以继续用上面那种方式。
  4. int updatePasswordByUsername(@Param("name") String username,   //我们可以使用@Param指定名称
  5.                              @Param("password") String newPassword);
复制代码
  1. @Test
  2. void updateAccount(){
  3.     repository.updatePasswordByUsername("Admin", "654321");
  4. }
复制代码
JPA关联查询

对一:会在写了注解(@OneToOne @MangToOne)的类中创建字段。对多:需要多的那方原本就有字段,才可以用该字段对应注解这方的主键
一对一

而用户信息和用户详细信息之间形成了一对一的关系,那么这时我们就可以直接在类中指定这种关系:
  1. @Data
  2. @Entity
  3. @Table(name = "users")
  4. public class Account {
  5.     @GeneratedValue(strategy = GenerationType.IDENTITY)
  6.     @Column(name = "id")
  7.     @Id
  8.     int id;
  9.     @Column(name = "username")
  10.     String username;
  11.     @Column(name = "password")
  12.     String password;
  13.     @JoinColumn(name = "detail_id")   //指定存储外键的字段名称。在本表中创建detail_id,并外键连接AccountDetail表的主键id
  14.     @OneToOne    //声明为一对一关系  
  15.     AccountDetail detail;
  16.     //执行代码:alter table users add constraint xxxxx foreign key (detail_id) references users_detail (id)
  17. }
复制代码
在修改实体类信息后,我们发现在启动时也进行了更新,日志如下:
  1. Hibernate: alter table users add column detail_id integer
  2. 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
  3. Hibernate: alter table users add constraint FK7gb021edkxf3mdv5bs75ni6jd foreign key (detail_id) references users_detail (id)
复制代码
测试查询:
  1. @Test
  2. void pageAccount() {
  3.     repository.findById(1).ifPresent(System.out::println);
  4. }
复制代码
结果:在建立关系之后,我们查询Account对象时,会自动将关联数据的结果也一并进行查询。
  1. 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=?
  2. Account(id=1, username=Test, password=123456, detail=AccountDetail(id=1, address=四川省成都市青羊区, email=8371289@qq.com, phone=1234567890, realName=本伟))
复制代码
懒加载

不想加载外键表的信息时,可以设置懒加载,这样只有在需要时才会向数据库获取:
设置懒加载后,使用懒加载设置过的属性时的方法需要在事务环境下获取(因为repository方法调用完后Session会立即关闭
  1. @JoinColumn(name = "detail_id")
  2. @OneToOne(fetch = FetchType.LAZY)    //将获取类型改为LAZY
  3. AccountDetail detail;
复制代码
接着我们测试一下:(测试类里开启事务会自动回滚,不想回滚则在方法前加@Commit。当然毕竟是测试类还是建议保留自动回滚)
  1. @Transactional   //懒加载属性需要在事务环境下获取,因为repository方法调用完后Session会立即关闭
  2. @Test
  3. void pageAccount() {
  4.     repository.findById(1).ifPresent(account -> {
  5.         System.out.println(account.getUsername());   //获取用户名
  6.         System.out.println(account.getDetail());  //获取详细信息(懒加载)
  7.     });
  8. }
复制代码
接着我们来看看控制台输出了什么:可以看到,获取用户名之前,并没有去查询用户的详细信息,而是当我们获取详细信息时才进行查询并返回AccountDetail对象。
  1. 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=?
  2. Test
  3. 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=?
  4. AccountDetail(id=1, address=四川省成都市青羊区, email=8371289@qq.com, phone=1234567890, realName=卢本)
复制代码
关联表之间操作一致

【提前更改实体类的设定】
  1. @JoinColumn(name = "detail_id")
  2. @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) //设置关联操作为ALL
  3. AccountDetail detail;
复制代码
可以多个并存,接着我们来进行一下测试:
  1. @Test
  2. void addAccount(){
  3.     Account account = new Account();
  4.     account.setUsername("Nike");
  5.     account.setPassword("123456");
  6.     AccountDetail detail = new AccountDetail();
  7.     detail.setAddress("重庆市渝中区解放碑");
  8.     detail.setPhone("1234567890");
  9.     detail.setEmail("73281937@qq.com");
  10.     detail.setRealName("张三");
  11.           account.setDetail(detail);
  12.     account = repository.save(account);        //记得用repository保存
  13.     System.out.println("插入时,自动生成的主键ID为:"+account.getId()+",外键ID为:"+account.getDetail().getId());
  14. }
复制代码
可以看到日志结果:结束后会发现数据库中两张表都同时存在数据。
  1. Hibernate: insert into users_detail (address, email, phone, real_name) values (?, ?, ?, ?)
  2. Hibernate: insert into users (detail_id, password, username) values (?, ?, ?)
  3. 插入时,自动生成的主键ID为:6,外键ID为:3
复制代码
一对多

接着我们来看一对多关联,比如每个用户的成绩信息:
  1. Account类:
  2. @JoinColumn(name = "uid")  //注意这里的name指的是Score表中的uid字段对应的就是当前的主键,会将uid外键设置为当前的主键
  3. //执行的语句为:alter table account_score add constraint xxxxx foreign key (uid) reference account(id)
  4. @OneToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL)  //在移除Account时,一并移除所有的成绩信息,依然使用懒加载
  5. List<Score> scoreList;
复制代码
  1. @Data
  2. @Entity
  3. @Table(name = "users_score")   //成绩表,注意只存成绩,不存学科信息,学科信息id做外键
  4. public class Score {
  5.     @GeneratedValue(strategy = GenerationType.IDENTITY)
  6.     @Column(name = "id")
  7.     @Id
  8.     int id;
  9.     @OneToOne   //一对一对应到学科上
  10.     @JoinColumn(name = "cid")
  11.     Subject subject;
  12.     @Column(name = "socre")
  13.     double score;
  14.     @Column(name = "uid")
  15.     int uid;
  16. }
复制代码
  1. @Data
  2. @Entity
  3. @Table(name = "subjects")   //学科信息表
  4. public class Subject {
  5.     @GeneratedValue(strategy = GenerationType.IDENTITY)
  6.     @Column(name = "cid")
  7.     @Id
  8.     int cid;
  9.     @Column(name = "name")
  10.     String name;
  11.     @Column(name = "teacher")
  12.     String teacher;
  13.     @Column(name = "time")
  14.     int time;
  15. }
复制代码
在数据库中填写相应数据,接着我们就可以查询用户的成绩信息了:成功得到用户所有的成绩信息,包括得分和学科信息。
  1. @Transactional
  2. @Test
  3. void test() {
  4.     repository.findById(1).ifPresent(account -> {
  5.         account.getScoreList().forEach(System.out::println);
  6.     });
  7. }
复制代码
多对一

同样的,我们还可以将对应成绩中的教师信息单独分出一张表存储,并建立多对一的关系,因为多门课程可能由同一个老师教授:
  1. Subjects表:
  2. @ManyToOne(fetch = FetchType.LAZY)
  3. @JoinColumn(name = "tid")   //存储教师ID的字段,和一对一是一样的,也会在当前表中创个外键tid,对应Teacher表的主键
  4. Teacher teacher;
  5. //执行的代码:
  6. //alter table subjects add colunm tid integer
  7. //alter table subjects add constraint xxxxx foreign key(tid) reference teachers (id)
复制代码
接着就是教师实体类了:
  1. @Data
  2. @Entity
  3. @Table(name = "teachers")
  4. public class Teacher {
  5.     @Column(name = "id")
  6.     @GeneratedValue(strategy = GenerationType.IDENTITY)
  7.     @Id
  8.     int id;
  9.     @Column(name = "name")
  10.     String name;
  11.     @Column(name = "sex")
  12.     String sex;
  13. }
复制代码
最后我们再进行一下测试:
  1. @Transactional
  2. @Test
  3. void test() {
  4.     repository.findById(3).ifPresent(account -> {
  5.         account.getScoreList().forEach(score -> {
  6.             System.out.println("课程名称:"+score.getSubject().getName());
  7.             System.out.println("得分:"+score.getScore());
  8.             System.out.println("任课教师:"+score.getSubject().getTeacher().getName());
  9.         });
  10.     });
  11. }
复制代码
成功得到多对一的教师信息。
多对多

最后我们再来看最复杂的情况,现在我们一门课程可以由多个老师教授,而一个老师也可以教授多个课程,那么这种情况就是很明显的多对多场景,现在又该如何定义呢?我们可以像之前一样,插入一张中间表表示教授关系,这个表中专门存储哪个老师教哪个科目:
  1. Subjects表:
  2. @ManyToMany(fetch = FetchType.LAZY)   //多对多场景
  3. //注意此操作,最后只会在该表多一个字段,和一个中间表,不会在另一个表中多一个字段。需要编写类似代码才会出现
  4. @JoinTable(name = "teach_relation",     //多对多中间关联表
  5.         joinColumns = @JoinColumn(name = "cid"),    //当前实体主键在关联表中的字段名称
  6.         inverseJoinColumns = @JoinColumn(name = "tid")   //教师实体主键在关联表中的字段名称,并在当前表中创建tid字段作为外键连接关联表的tid
  7. )
  8. List<Teacher> teacher;
复制代码
接着,JPA会自动创建一张中间表,并自动设置外键,我们就可以将多对多关联信息编写在其中了。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4