学习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数据存储库支持)
JPA 是 hibernate 的一个抽象(就像JDBC和JDBC驱动的关系)
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的时候就不是固定的了(配置文件这里面不细说了)
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="jpa-1" transaction-type="RESOURCE_LOCAL">
<!--
配置使用什么 ORM 产品来作为 JPA 的实现
1. 实际上配置的是 javax.persistence.spi.PersistenceProvider 接口的实现类
2. 若 JPA 项目中只有一个 JPA 的实现产品, 则也可以不配置该节点.
-->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- 添加持久化类 -->
<class>com.atguigu.jpa.helloworld.Manager</class>
<properties>
<!-- 连接数据库的基本信息 -->
<property name="javax.persistence.jdbc.driver"
value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="1234"/>
<!-- 配置 JPA 实现产品的基本属性. 配置 hibernate 的基本属性 -->
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
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的配置文件中配置
<!-- 配置 EntityManagerFactory -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!-- 配置 JPA 提供商的适配器. 可以通过内部 bean 的方式来配置 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
</property>
<!-- 配置实体类所在的包 -->
<property name="packagesToScan" value="com.atguigu.jpa.spring.entities"></property>
<!-- 配置 JPA 的基本属性. 例如 JPA 实现产品的属性 -->
<property name="jpaProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
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)方法变为游离态。
jpa操作数据主要是em的方法进行操作
查询方法
•find (Class<T> entityClass,Object primaryKey):返回指定的 OID 对应的实体类对象,如果这个实体存在于当前的持久化环境,则返回一个被缓存的对象;否则会创建一个新的 Entity, 并加载数据库中相关信息;若 OID 不存在于数据库中,则返回一个 null。第一个参数为被查询的实体类类型,第二个参数为待查找实体的主键值。类似于 hibernate 中 Session 的 get 方法.
•getReference (Class<T> entityClass,Object primaryKey):与find()方法类似,不同的是:如果缓存中不存在指定的 Entity, EntityManager 会创建一个 Entity 类的代理,但是不会立即加载数据库中的信息,只有第一次真正使用此 Entity 的属性才加载,所以如果此 OID 在数据库不存在,getReference() 不会返回 null 值, 而是抛出EntityNotFoundException 。类似于 hibernate 中 Session 的 load 方法(懒加载)
增加方法
•persist (Object entity):用于将新创建的 Entity 纳入到 EntityManager 的管理。该方法执行后,传入 persist() 方法的 Entity 对象转换成持久化状态。
–如果传入 persist() 方法的 Entity 对象已经处于持久化状态,则 persist() 方法什么都不做。
–如果对删除状态的 Entity 进行 persist() 操作,会转换为持久化状态。
–如果对游离状态的实体执行 persist() 操作,可能会在 persist() 方法抛出 EntityExistException(也有可能是在flush或事务提交后抛出)。
删除方法
•remove (Object entity):删除实例。如果实例是被管理的,即与数据库实体记录关联,则同时会删除关联的数据库记录。
注意该方法只能用于删除持久对象
增加修改方法
•merge (T entity):merge() 用于处理 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<Customer> 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<Blog> list = blogService.getLike(hql);
==============================================不友好的错误示范---
"from user u where u.username like '%?1%'"
jpa 操作mysql 日期
实体类
@Temporal(TemporalType.TIMESTAMP)
private Date updateTime;
数据库
updateTime datetime(类型)
//传入string 类型
public List<Blog> 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<Blog> blogs = blogDao.queryByStringDate("2019-08-28 10:47:26");
-----------------------------------------------------------------------
//传入date类型
public List<Blog> 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<Blog> blogs = blogDao.queryByDate(new Date());
以上就是介绍jpa操作数据的em操作数据
下面介绍SpringDataJpa使用各种Repository 接口操作数据。
–Repository: 仅仅是一个标识,表明任何继承它的均为仓库接口类
–CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法
–PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法
–JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法
重点:
Repository 是一个空接口. 即是一个标记接口 若我们定义的接口继承了 Repository, 则该接口会被 IOC 容器识别为一个 Repository Bean. 纳入到 IOC 容器中. 进而可以在该接口中定义满足一定规范的方法.
所以我们一般只要继承了JpaRepository的接口就可以了。(这一步是必须的)
也可以使用如下方法:
@RepositoryDefinition(domainClass=Person.class,idClass=Integer.class)
这里我一般使用@Query自定义查询,比较灵活。
demo:
用参数 必须要使用@Param得到里面的参数值
同时也可以使用个类似hibernate的投影查询,但是必须要有对应的构造方法
@Query("select new Person (p.lastName,p.email) from Person p where p.age=:age")
List<Person> getPersons(@Param("age") Integer age);
也可以使用占位符的方式如下:
@Query("select new Person (p.lastName,p.email) from Person p where p.age=?1")
List<Person> 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 extends T> S save(S entity); 保存给定的实体。
void delete(T entity); 删除给定的实体。
List<T> findAll();//查询所有
T findOne(ID var1);//查询返回指定id 的实体
几个demo过后用的确实很爽,所以还是忍不住想看看底层实现原理:对应findOne()通过debug断点:
public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
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<T> 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<Person> getPersons(Integer age, Sort sort);
使用:
List<Person> 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<Person> 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对数据进行操作。
直接注入进来em
@PersistenceContext
private EntityManager entityManager;
然后用em进行jpql进行em方法操作
如此springdatajpa 和jpa的一些操作数据的方法就介绍完了,还算完整。
---------------------------------------------------------------
其实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<Order> 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<Item> 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<Category> getCategories() {
return categories;
}
使用copy,用到时候可以直接复制呵呵哒
@ManyToMany
@JoinTable(name="中间表名称",
joinColumns=@joinColumn(name="本类的外键",referencedColumnName="本类与外键对应的主键"),
inversejoinColumns=@JoinColumn(name="对方类的外键",
referencedColunName="对方类与外键对应的主键")
)
666666666!!!