赞
踩
JPA 简介:
JPA(Java Persistence API)是 Java 持久层规范,定义了一些列 ORM 接口,它本身是不能直接使用的,因为接口需要实现才能使用,Hibernate 框架就是实现 JPA 规范的框架。
SpringData JPA 简介
Spring Data JPA 是 Spring 框架提供的对 JPA 规范的抽象,通过约定的命名规范完成持久层接口的编写,在不需要实现接口的情况下,就可以实现对数据库的操作。简单理解就是通过 Spring Data JPA,你只需要定义接口而不需要实现,就能完成 CRUD 操作。
Spring Data JPA 本身并不是一个具体实现,它只是一个抽象层,底层还是需要 Hibernate 这样的 JPA 实现来提供支持。
Spring Data JPA 是一个全自动化的 ORM 框架,底层是 Hibernate 框架提供支持,开发者只需要调用接口方法即可,不必关心 SQL 语句和结果集的解析,非常方便
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.3.2.RELEASE</version>
- <relativePath/>
- </parent>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-jpa</artifactId>
- </dependency>
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
/src/main/resources/application.properties 配置文件中
- ## 配置数据源
- spring:
- datasource:
- url: jdbc:mysql://127.0.0.1:3306/wdzldb?useSSL=false&serverTimezone=Asia/Shanghai
- username: root
- driver-class-name: com.mysql.cj.jdbc.Driver
- password: root
- jpa:
- show-sql: true
- properties:
- hibernate:
- format_sql: true # 格式化SQL
- # 生成SQL 驼峰命名属性,默认加下划线的情况,下面配置可以去掉下划线。
- hibernate:
- naming:
- physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
注意:这里使用的注解和 SpringData JDBC 的不同,属性名出现 驼峰命名的,也会默认映射为 book_name 的形式,此时需要通过 @Column 注解来映射。也可以在配置文件中统一配置关闭下划线。
注意:常见错误
- Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.wdzl.pojo.Book
- at org.hibernate.metamodel.internal.MetamodelImpl.managedType(MetamodelImpl.java:582)
- at org.hibernate.metamodel.internal.MetamodelImpl.managedType(MetamodelImpl.java:85)
原因:实体类没有指定@Entry 注解。 添加注解后,会显示编译错误,要求必须指定主键 @Id
主键需要指定主键生成策略,否则默认的是
GenerationType strategy() default GenerationType.AUTO;
默认的策略是序列,应用于 Oracle 数据库
- package com.wdzl.pojo;
-
- import lombok.Data;
-
- import javax.persistence.*;
- import java.util.Date;
-
- @Data
- @Entity //要求必须有@Id
- public class Book {
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- @Column(name="bookid")
- private Integer bookId;
- @Column(name="bookname")
- private String bookName;
- private Float price;
- @Column
- private Date pubdate;
- private String author;
- }
注意:这里继承的接口是 JpaRepository
在 JpaRepository 接口中包含了继承的 CRUD 方法,如果需要组合条件查询,可以遵循 “约定大于配置” 策略,实现组合条件查询。
注意异常:
- Caused by: java.lang.IllegalArgumentException: Not a managed type: class java.lang.Object
- at org.hibernate.metamodel.internal.MetamodelImpl.managedType(MetamodelImpl.java:582) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
- at org.hibernate.metamodel.internal.MetamodelImpl.managedType(MetamodelImpl.java:85) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
原因:没有指定泛型: extends JpaRepository<Book,Integer>
IBookDao 类代码如下:
下面方法通过默认约定名来实现
- import com.wdzl.pojo.Book;
- import org.springframework.data.jpa.repository.JpaRepository;
- import org.springframework.stereotype.Repository;
-
- import java.util.List;
-
- @Repository
- public interface IBookDao extends JpaRepository<Book,Integer> {
- // bookname=?
- List<Book> findBookByBookName(String bookName);
-
- // 等价于 between price ? and ?
- List<Book> findBookByPriceBetween(Float price1,Float price2);
-
- //等价于 price>?
- List<Book> findBookByPriceAfter(Float price);
- //bookName like ? ==='%实%'
- List<Book> findBookByBookNameContaining(String bookName);
- //bookName like ? ==='%实%'
- List<Book> findBookByBookNameContains(String bookName);
-
- // bookName like '%入门'
- List<Book> findBookByBookNameEndsWith(String bookName);
-
- }
也可以通过 HQL 来定义方法的查询,因为 SpringData Jpa 通过 Hibernate 实现的。
- //注意下面是 HQL
- @Query("from Book where bookName like :bookname and price > :price")
- List<Book> findCondtion(String bookname,Float price);
注意:不支持 select *
- @Service("bookService")
- public class BookService {
- @Autowired
- private IBookDao bookDao;
- public List<Book> queryAll(){
- return bookDao.findAll(); //这里不同jdbc 这里直接返回 list
- }
- public void save(Book book){
- bookDao.save(book);
- }
-
- public long count(){
- return bookDao.count();
- }
- }
注意:测试类 一定要和启动类同包,这里是代替了启动类
- package com.wdzl;
-
- import com.wdzl.pojo.Book;
- import com.wdzl.service.BookService;
- import org.junit.jupiter.api.Test;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.boot.test.context.SpringBootTest;
-
- import java.util.Date;
-
- @SpringBootTest
- @SpringBootApplication //注意这里一定要和被测试类在同包 即启动类的位置
- public class TestApplication {
- @Autowired
- private BookService bookService;
-
- @Test
- public void testQuery(){
- bookService.queryAll().forEach(book -> System.out.println(book));
- }
-
- @Test
- public void save(){
- Book book = new Book();
- book.setPubdate(new Date());
- book.setBookName("Java入门与提高");
- book.setPrice(34F);
- bookService.save(book); //主键不存在 执行插入
- }
- @Test
- public void update(){
- Book book = new Book();
- book.setBookId(9);
- book.setPubdate(new Date());
- book.setBookName("Java基础入门");
- book.setPrice(24F);
- bookService.save(book); //主键存在执行修改
- }
- }
注意:
在执行 save() 保存时,如果设置了主键,则会先进行查询,再根据查询结果来执行操作,如果查询记录存在,则修改。否则不存在则插入。
如果主键策略没有指定自动增长,则在执行 save() 时,必须要设置主键。否则执行失败。
下面是设置了主键,但是数据库表中不存在的执行效果:
先执行了查询,再执行了 insert 插入操作
- Hibernate:
- select
- book0_.bookid as bookid1_0_0_,
- book0_.author as author2_0_0_,
- book0_.bookname as bookname3_0_0_,
- book0_.price as price4_0_0_,
- book0_.pubdate as pubdate5_0_0_
- from
- book book0_
- where
- book0_.bookid=?
- Hibernate:
- insert
- into
- book
- (author, bookname, price, pubdate)
- values
- (?, ?, ?, ?)
注意:
从上面控制台打印的结果来看,首先底层使用的Hibernate实现,
其次,先根据指定主键执行了查询来判断是否存在记录。
最后因为没有查询到记录,而执行 insert 操作。
- @NoRepositoryBean
- public interface 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);
-
- <S extends T> List<S> saveAllAndFlush(Iterable<S> var1);
-
- /** @deprecated */
- @Deprecated
- default void deleteInBatch(Iterable<T> entities) {
- this.deleteAllInBatch(entities);
- }
-
- void deleteAllInBatch(Iterable<T> var1);
-
- void deleteAllByIdInBatch(Iterable<ID> var1);
-
- void deleteAllInBatch();
-
- /** @deprecated */
- @Deprecated
- T getOne(ID var1);
-
- T getById(ID var1);
-
- <S extends T> List<S> findAll(Example<S> var1);
-
- <S extends T> List<S> findAll(Example<S> var1, Sort var2);
- }
- public void save(){
- Book book = new Book();
- book.setBookName("MySQL性能优化");
- book.setPrice(45f);
- bookDao.save(book);
- }
- public void update(){
- Book book = bookDao.getById(25);
- book.setPubdate(new Date());
- book.setBookName("MySQL性能优化");
- book.setPrice(45f);
- bookDao.save(book);
- }
注意:
修改使用save方法,如果新实例化的对象,如果有主键且表中存在,则直接修改操作
如果实例化对象,同时指定主键,但不存在,也会自动插入操作。
但是,如果先查询,再修改,则默认情况下会抛出异常:
org.hibernate.LazyInitializationException: could not initialize proxy [com.wdzl.pojo.Book#25] - no Session
原因是默认使用了延时加载策略,这样在查询后已经关闭了session,但有修改操作了没加载的属性。导致异常。
解决办法:
在实体类上添加注解,关闭延时加载 : @Proxy(lazy = false) 或 放入事务单元
- @Data
- @Entity
- @Proxy(lazy = false)
- public class Book {
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Integer bookId;
- private String bookName;
- private Float price;
-
- private Date pubdate;
- private String author;
- }
第二种修改方式:使用HQL语句修改,但不支持insert .
注意:事务和允许修改注解 @Transactional 和 @Modifying
- @Modifying
- @Query("update Book set price=:price where bookId=:bookid") //类名和属性名
- @Transactional
- int update(Float price,Integer bookid);
源码如下:
- @Deprecated
- default void deleteInBatch(Iterable<T> entities) { //已经过时,不用关注
- this.deleteAllInBatch(entities);
- }
-
- void deleteAllInBatch(Iterable<T> var1); //按对象批量删除
-
- void deleteAllByIdInBatch(Iterable<ID> var1); //按主键批量删除
-
- void deleteAllInBatch(); //全部删除
案例应用:
- //批量删除,
- // 生成SQL: Hibernate: delete from Book where bookId in (? , ? , ? , ?)
- public void delByBatch(){
- List<Integer> ids = Arrays.asList(3,23,12,45);
- bookDao.deleteAllByIdInBatch(ids);
- }
也可以使用 HQL语句来删除,也需要事务支持。
- @Modifying
- @Query("delete from Book where bookName=:bookName")
- @Transactional
- int delete(String bookName);
也支持按约定规则命名方式来删除,注意:也需要事务支持
- @Transactional
- void deleteBookByAuthor(String author);
- // between
- List<Book> findBooksByPriceBetween(Float min,Float max);
- // 模糊查询 like 语句
- List<Book> findBooksByBookNameLike(String bookName);
生成 SQL如下:
- Hibernate: select book0_.bookId as bookid1_0_, book0_.author as author2_0_, book0_.bookName as bookname3_0_, book0_.price as price4_0_, book0_.pubdate as pubdate5_0_ from Book book0_ where book0_.author=?
- Hibernate: delete from Book where bookId=?
从上面可以看出,在删除时,先做查询,再做删除。如果查询不到,则不执行删除操作;
- List<T> findAll(); //查询全部
-
- List<T> findAll(Sort var1); //排序查询
-
- List<T> findAllById(Iterable<ID> var1); //按多个主键查询
-
- T getById(ID var1);
-
- <S extends T> List<S> findAll(Example<S> var1); // 条件查询
-
- <S extends T> List<S> findAll(Example<S> var1, Sort var2);
- //条件查询
- public void findBookByExample(){
- Book book = new Book();
- book.setBookName("Java");
- book.setPrice(23f);
- book.setBookId(23);
- Example<Book> bookExample = Example.of(book);
- bookDao.findAll(bookExample);
- }
- //可以按指定属性排序
- public void querySort(){
- //Order.desc()|asc()分别指定降序和升序
- Sort price = Sort.by(Sort.Order.desc("price"));
- List<Book> all = bookDao.findAll(price);
- all.forEach(System.out::println);
- }
- public void queryByPage(){
- //分页查询 第一个参数:页号(从0开始),第二参数:每页显示记录数
- Pageable pageable = PageRequest.of(2,2);
- bookDao.findAll(pageable).forEach(System.out::println);
- }
下面配置多对一关联关系。图书类关联依赖作者类。
注意:不使用@Data 为了避免 toString(),在双向多对一时,产生死循环。
图书类:
- @Getter
- @Setter
- @Entity
- //@Proxy(lazy = false)
- public class Book {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Integer bookId;
- private String bookName;
- private Float price;
-
- private Date pubdate;
- // private String author;
- //
- @JoinColumn(name="author") //关联的外键
- @ManyToOne(fetch = FetchType.LAZY)
- private Author author;
- }
作者类:
- @Entity(name="usertbl")
- @Getter
- @Setter
- public class Author {
- @Id
- private Integer userid;
- private String username;
- private String email;
-
- @OneToMany(mappedBy = "author",fetch = FetchType.LAZY)
- private Set<Book> books = new HashSet<>();
- }
图书关联查询作者:
- @Transactional
- public void manyToOne(){
- Book book = bookDao.getById(22);
- System.out.println(book.getBookName()+"=="+book.getAuthor());
- }
作者关联查询图书:
- @Transactional
- public void query(){
- Author author = authorDao.findAuthorByUserid(5);
- Set<Book> books = author.getBooks();
- books.forEach(s-> System.out.println(s.getBookName()));
- }
常见错误:
org.hibernate.LazyInitializationException: could not initialize proxy [com.wdzl.pojo.Book#22] - no Session
原因:
默认关联对象是延时加载的,在查询对象后默认关闭session,在后面使用到关联属性或对象时,再去查询时,已经不存在session可用了。所以抛出异常。
解决办法:
可以使用事务,让整个方法在一个事务单元中,使用完毕后再自动关闭session.
HQL(Hibernate Query Language)是 Hibernate 框架提供的面向对象的查询语言,它是 SQL 的一个超集,主要用来与关系型数据库进行交互,但它使用的是面向对象的方式来表达查询。HQL 查询的对象不再是数据库表,而是实体类及其属性。
- /**
- * HQL
- */
- @Repository
- public interface IBookDao extends JpaRepository<Book,Integer> {
- //支持HQL
- // @Query("select book from Book book")
- @Query("from Book")
- List<Book> queryAllBook();
-
- @Query("select bookId,bookName,author from Book")
- List<Object[]> queryAll();
-
- /**
- * 如果要修改,需要添加注解 @Modifying @Transactional
- * @param price
- * @param bookid
- * @return
- */
- @Modifying
- @Query("update Book set price=:price where bookId=:bookid")
- @Transactional
- int update(Float price,Integer bookid);
-
- @Modifying
- @Query("delete from Book where bookName=:bookName")
- @Transactional
- int delete(String bookName);
-
- @Transactional
- void deleteBookByBookName(String bookname);
-
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。