学习springboot肯定要进行和一些其可以操作数据的框架进行整合,所以在进行和spring data JPA整合的时候,首先需要了解springdata 和 jpa(简直吐血),了解一些原理,在整合之后使用才能得心应手。
知识点:
官网链接
Spring Data Jpa是spring data 主要的子模块只之一,对于spring data的介绍
Spring Data JPA - Spring Data repository support for JPA.(翻译:Spring Data JPA—对JPA的Spring数据存储库支持)
Java Persistence API(jpa)
JPA 是规范:JPA 本质上就是一种 ORM 规范,不是ORM 框架 —— 因为 JPA 并未提供 ORM 实现,它只是制订了一些规范,提供了一些编程的 API 接口,但具体实现则由 ORM 厂商提供实现
Hibernate 是实现:Hibernate 除了作为 ORM 框架之外,它也是一种 JPA 实现
JPA默认使用hibernate作为ORM实现,所以,一般使用Spring Data JPA即会使用hibernate。
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。
使用jpa操作数据需要导入hibernate必须的包,和hibernate对jpa支持的依赖,然后就是需要创建一个在类路径的 META-INF 目录下放置persistence.xml,文件的名称是固定的,然后进行增删改查操作。整合spring的时候就不是固定的了(配置文件这里面不细说了)
org.hibernate.ejb.HibernatePersistence
com.atguigu.jpa.helloworld.Manager
String persistenceUnitName = "jpa-1";
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(persistenceUnitName);
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
// String hql="from Customer e where e.age=?";
// Query query = entityManager.createQuery(hql);
// query.setParameter(1, 1);
// List resultList = query.getResultList();
transaction.commit();
entityManager.close();
entityManagerFactory.close();
对于jpa,EntityManager 是整个累的核心,就像hibernate里面的session
我们知道spring属于框架的框架,它可以整合很多其它框架,其中Spring 框架对 JPA也是支持的。
Spring 提供的LocalContainerEntityManagerFactoryBean 提供了非常灵活的配置,persistence.xml中的信息都可以在此以属性注入的方式提供。在spring的配置文件中配置
true
true
update
Spring 将 EntityManager的创建与销毁、事务管理等代码抽取出来,并由其统一管理,开发者不需要关心这些,如前面的代码所示,业务方法中只剩下操作领域对象的代码,事务管理和EntityManager 创建、销毁的代码都不再需要开发者关心了。
//如何获取到和当前事务关联的 EntityManager 对象呢 ?
//通过 @PersistenceContext 注解来标记成员变量!
@PersistenceContext
private EntityManager entityManager;
Spring Data JPA 框架的出现让我们连实现持久层业务逻辑都省了,唯一要做的,就只是声明持久层的接口,其他都交给 Spring
Data JPA 来帮你完成!666!
需要下载下载 Spring Data Commons 和 Spring Data JPA 两个发布包!
SpringData 项目所支持的关系数据存储技术:JDBC,JPA。
在hibernate学习中我们知道对象的三个状态临时态,游离态,持久态
临时状态我们知道就是刚new 处一对象,没有oid(对象标识符),没有session,持久太就是存在数据库中的永久数据,有oid 也存在session中,游离态就是有oid ,没存在session中
临时状态可以通过一些保存(save)操作变为持久态,游离态也可以通过保存操作(save)变为持久太,持久太可以通过关闭session的(close)方法变为游离态。
•find (Class
•getReference (Class
•persist (Object entity):用于将新创建的 Entity 纳入到 EntityManager 的管理。该方法执行后,传入 persist() 方法的 Entity 对象转换成持久化状态。
–如果传入 persist() 方法的 Entity 对象已经处于持久化状态,则 persist() 方法什么都不做。
–如果对删除状态的 Entity 进行 persist() 操作,会转换为持久化状态。
–如果对游离状态的实体执行 persist() 操作,可能会在 persist() 方法抛出 EntityExistException(也有可能是在flush或事务提交后抛出)。
•remove (Object entity):删除实例。如果实例是被管理的,即与数据库实体记录关联,则同时会删除关联的数据库记录。
注意该方法只能用于删除持久对象
对于游离状态,数据库中有对应记录就执行修改方法,没有就执行增加方法,但是要注意会返回一个新对象,对新对象进行插入修改操作。
----------------------------------------------------------------------------------------------------------------------------------------------------------
•Query接口封装了执行数据库查询的相关方法。调用 EntityManager 的 createQuery、create NamedQuery 及 createNativeQuery 方法可以获得查询对象,进而可调用 Query 接口的相关方法来执行查询操作。
•createQuery (String qlString):创建一个查询对象。
•createNamedQuery (String name):根据命名的查询语句块创建查询对象。参数为命名的查询语句。
•createNativeQuery (String sqlString):使用标准 SQL语句创建查询对象。参数为标准SQL语句字符串。
Query接口的主要方法
int executeUpdate()用于执行update或delete语句。
List getResultList()用于执行select语句并返回结果集实体列表。
Object getSingleResult()用于执行只返回单个结果实体的select语句。
Query setFirstResult(int startPosition)用于设置从哪个实体记录开始返回查询结果。
Query setMaxResults(int maxResult) 用于设置返回结果实体的最大数。与setFirstResult结合使用可实现分页查询。
Query setFlushMode(FlushModeType flushMode)
设置查询对象的Flush模式。参数可以取2个枚举值:FlushModeType.AUTO 为自动更新数据库记录,
FlushMode Type.COMMIT 为直到提交事务时才更新数据库记录。
setParameter(String name, Object value) 为查询语句的指定名称参数赋值。
setParameter(int position, Object value) 为查询语句的指定位置参数赋值。
Position 指定参数序号,value 为赋给参数的值。
•JPQL语言,即 Java Persistence Query Language 的简称。JPQL 是一种和 SQL 非常类似的中间性和对象化查询语言,它最终会被编译成针对不同底层数据库的 SQL 查询,从而屏蔽不同数据库的差异。类似于hql语言。
@Test
public void testHelloJPQL(){
String jpql = "FROM Customer c WHERE c.age > ?";
Query query = entityManager.createQuery(jpql);
//占位符的索引是从 1 开始
query.setParameter(1, 1);
List customers = query.getResultList();
System.out.println(customers.size());
}
//可以使用 JPQL 完成 UPDATE 和 DELETE 操作.
@Test
public void testExecuteUpdate(){
String jpql = "UPDATE Customer c SET c.lastName = ? WHERE c.id = ?";
Query query = entityManager.createQuery(jpql).setParameter(1, "YYY").setParameter(2, 12);
query.executeUpdate();
}
//createNativeQuery 适用于本地 SQL
@Test
public void testNativeQuery(){
String sql = "SELECT age FROM jpa_cutomers WHERE id = ?";
Query query = entityManager.createNativeQuery(sql).setParameter(1, 3);
Object result = query.getSingleResult();
System.out.println(result);
}
//createNamedQuery 适用于在实体类前使用 @NamedQuery 标记的查询语句,所以需要在实体类头上
标记@NameQuery注解,如下
@Test
public void testNamedQuery(){
Query query = entityManager.createNamedQuery("testNamedQuery").setParameter(1, 3);
Customer customer = (Customer) query.getSingleResult();
System.out.println(customer);
}
@NamedQuery(name="testNamedQuery", query="FROM Customer c WHERE c.id = ?")
@Table(name="JPA_CUTOMERS")
@Entity
public class Customer {
}
模糊查询 使用CONCAT函数拼接
String hql="from Blog b where b.title like CONCAT('%',?1,'%') or b.content like CONCAT('%',?1,'%')";
等于
String hql="from Blog b where b.title like ?1 or b.content like ?1";
然后在controller或者service层进行拼接
String hql="%计%";
List list = blogService.getLike(hql);
==============================================不友好的错误示范---
"from user u where u.username like '%?1%'"
实体类
@Temporal(TemporalType.TIMESTAMP)
private Date updateTime;
数据库
updateTime datetime(类型)
//传入string 类型
public List queryByStringDate(String date) {
String hql="from Blog b where b.updateTime =?1";
Query query = em.createQuery(hql).setParameter(1, DateUtils.toDate(date));//转成date 类型
List list = query.getResultList();
return list;
}
测试:
List blogs = blogDao.queryByStringDate("2019-08-28 10:47:26");
-----------------------------------------------------------------------
//传入date类型
public List queryByDate(Date date) {
String hql="from Blog b where b.updateTime < ?1";
log.info("date={}",date);
Query query = em.createQuery(hql).setParameter(1,date);
List list = query.getResultList();
return list;
}
测试:
List blogs = blogDao.queryByDate(new Date());
–Repository: 仅仅是一个标识,表明任何继承它的均为仓库接口类
–CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法
–PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法
–JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法
重点:
Repository 是一个空接口. 即是一个标记接口 若我们定义的接口继承了 Repository, 则该接口会被 IOC 容器识别为一个 Repository Bean. 纳入到 IOC 容器中. 进而可以在该接口中定义满足一定规范的方法.
也可以使用如下方法:
@RepositoryDefinition(domainClass=Person.class,idClass=Integer.class)
这里我一般使用@Query自定义查询,比较灵活。
用参数 必须要使用@Param得到里面的参数值
同时也可以使用个类似hibernate的投影查询,但是必须要有对应的构造方法
@Query("select new Person (p.lastName,p.email) from Person p where p.age=:age")
List getPersons(@Param("age") Integer age);
也可以使用占位符的方式如下:
@Query("select new Person (p.lastName,p.email) from Person p where p.age=?1")
List getPersons(Integer age);
SpringData 允许在占位符上添加 %%. 如%?1% 或者%age%
//设置 nativeQuery=true 即可以使用原生的 SQL 查询,直接查询表
@Query(value="SELECT count(id) FROM jpa_persons", nativeQuery=true)
long getTotalCount();
可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT
在 @Query 注解中编写 JPQL 语句, 但必须使用 @Modifying 进行修饰. 以通知 SpringData, 这是一
个 UPDATE 或 DELETE 操作
UPDATE 或 DELETE 操作需要使用事务, 此时需要定义 Service 层. 在 Service 层的方法上或者类上面添加事务
操作.
默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作!
@Modifying
@Query("update Person p set p.email=?1 where p.id=?2")
int updatePersonEmail(String email,Integer id);
简单条件查询: 查询某一个实体类或者集合 ,按照 Spring Data 的规范,查询方法以 find | read | get 开头,
涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写。 (这里不demo了)
开始使用接口了,没看过底层,其实有点难用(懂了,自然就感到easy了)
总结一下常用的增删改查
S save(S entity); 保存给定的实体。
void delete(T entity); 删除给定的实体。
List findAll();//查询所有
T findOne(ID var1);//查询返回指定id 的实体
几个demo过后用的确实很爽,所以还是忍不住想看看底层实现原理:对应findOne()通过debug断点:
public class SimpleJpaRepository implements JpaRepository, JpaSpecificationExecutor {
private final EntityManager em;
public T findOne(ID id) {
Assert.notNull(id, "The given id must not be null!");
LockModeType type = this.lockMetadataProvider == null?null:this.lockMetadataProvider.getLockModeType();
Class domainType = this.getDomainClass();
return type == null?this.em.find(domainType, id):this.em.find(domainType, id, type);
}
}
这里我们可以看到其实就是使用EntityManager 的find()方法查询
继续看是否都是封装 EntityManager 里面的方法呢?在类SimpleJpaRepository我们可以看到其实底层都是调用em的方法。
springdata还以用sort进行排序查询,这里来个demo:
@Query("select new Person (p.lastName,p.email) from Person p where p.age=?1")
List getPersons(Integer age, Sort sort);
使用:
List persons = bean.getPersons(12, new Sort(Sort.Direction.ASC, "email"));
效果:
from
jpa_persons person0_ f
where
person0_.age=?
order by
person0_.email asc
最后来个分页demo:
@Test
public void testPagingAndSortingRespository(){
//pageNo 从 0 开始.
int pageNo = 6 - 1;
int pageSize = 5;
//Pageable 接口通常使用的其 PageRequest 实现类. 其中封装了需要分页的信息
//排序相关的. Sort 封装了排序的信息
//Order 是具体针对于某一个属性进行升序还是降序.
Order order1 = new Order(Direction.DESC, "id");
Order order2 = new Order(Direction.ASC, "email");
Sort sort = new Sort(order1, order2);
PageRequest pageable = new PageRequest(pageNo, pageSize, sort);
Page page = personRepsotory.findAll(pageable);
System.out.println("总记录数: " + page.getTotalElements());
System.out.println("当前第几页: " + (page.getNumber() + 1));
System.out.println("总页数: " + page.getTotalPages());
System.out.println("当前页面的 List: " + page.getContent());
System.out.println("当前页面的记录数: " + page.getNumberOfElements());
}
然后用em进行jpql进行em方法操作
其实spring整合这些框架,我们除了经常使用一些方法以外,还有实体之间的映射关系使我们关注的重点:
比如hibernate的xml配置的映射关系,jpa注解的映射关系。
之前其实有配置过hibernate实体类之间的映射关系!
这里介绍jpa的映射关系
双向多对一,一对多的关系
//映射单向 1-n 的关联关系
//使用 @OneToMany 来映射 1-n 的关联关系
//使用 @JoinColumn 来映射外键列的名称
//可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略
//可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略.
//注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了.
// @JoinColumn(name="CUSTOMER_ID")
@OneToMany(fetch=FetchType.LAZY,cascade={CascadeType.REMOVE},mappedBy="customer")
public Set getOrders() {
return orders;
}
//映射单向 n-1 的关联关系
//使用 @ManyToOne 来映射多对一的关联关系
//使用 @JoinColumn 来映射外键.
//可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
@JoinColumn(name="CUSTOMER_ID")
@ManyToOne(fetch=FetchType.LAZY)
public Customer getCustomer() {
return customer;
}
双向一对一
基于外键的 1-1 关联关系:在双向的一对一关联中,
需要在关系被维护端(inverse side)中的 @OneToOne
注释中指定 mappedBy,以指定是这一关联中的被维护端。
同时需要在关系维护端(owner side)建立外键列指向关系被维护端的主键列。
双向多对多,我们知道,多对多需要有第三张表所以:
我们必须指定一个关系维护端(owner side),可以通过 @ManyToMany 注释中指定 mappedBy 属性来标识其为关系维护端。
@ManyToMany(mappedBy="categories")//被维护一方
public Set- getItems() {
return items;
}
维护一方映射中间表
//使用 @ManyToMany 注解来映射多对多关联关系
//使用 @JoinTable 来映射中间表
//1. name 指向中间表的名字
//2. joinColumns 映射当前类所在的表在中间表中的外键
//2.1 name 指定外键列的列名
//2.2 referencedColumnName 指定外键列关联当前表的哪一列
//3. inverseJoinColumns 映射关联的类所在中间表的外键
@JoinTable(name="ITEM_CATEGORY",
joinColumns={@JoinColumn(name="ITEM_ID", referencedColumnName="ID")},
inverseJoinColumns={@JoinColumn(name="CATEGORY_ID", referencedColumnName="ID")})
@ManyToMany
public Set
getCategories() {
return categories;
}
使用copy,用到时候可以直接复制呵呵哒
@ManyToMany
@JoinTable(name="中间表名称",
joinColumns=@joinColumn(name="本类的外键",referencedColumnName="本类与外键对应的主键"),
inversejoinColumns=@JoinColumn(name="对方类的外键",
referencedColunName="对方类与外键对应的主键")
)
666666666!!!