SpringDataJpa 操作系列

学习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 的一个抽象就像JDBCJDBC驱动的关系)

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 createQuerycreate 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!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值