JPA(三)

本文介绍了JPA的使用,包括二级缓存的配置与测试。重点讲解了JPQL语言,它类似SQL,通过Query接口封装执行,涵盖select、update、delete等语句,还介绍了各语句的子句及函数。此外,阐述了Spring整合JPA的三种方式及相关测试。

使用二级缓存

在这里插入图片描述


配置二级缓存:

<?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.Customer</class>
		<class>com.atguigu.jpa.helloworld.Order</class>
	
		<class>com.atguigu.jpa.helloworld.Department</class>
		<class>com.atguigu.jpa.helloworld.Manager</class>
	
		<class>com.atguigu.jpa.helloworld.Item</class>
		<class>com.atguigu.jpa.helloworld.Category</class>
		
		<!-- 
		配置二级缓存的策略 
		ALL:所有的实体类都被缓存
		NONE:所有的实体类都不被缓存. 
		ENABLE_SELECTIVE:标识 @Cacheable(true) 注解的实体类将被缓存
		DISABLE_SELECTIVE:缓存除标识 @Cacheable(false) 以外的所有实体类
		UNSPECIFIED:默认值,JPA 产品默认值将被使用
		-->
		<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
	
		<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="1230"/>
			
			<!-- 配置 JPA 实现产品的基本属性. 配置 hibernate 的基本属性 -->
			<property name="hibernate.format_sql" value="true"/>
			<property name="hibernate.show_sql" value="true"/>
			<property name="hibernate.hbm2ddl.auto" value="update"/>
			
			<!-- 二级缓存相关 -->
			<property name="hibernate.cache.use_second_level_cache" value="true"/>
			<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
			<property name="hibernate.cache.use_query_cache" value="true"/>
		</properties>
	</persistence-unit>
</persistence>

二级缓存的产品:

<ehcache>

    <!-- Sets the path to the directory where cache .data files are created.

         If the path is a Java System Property it is replaced by
         its value in the running VM.

         The following properties are translated:
         user.home - User's home directory
         user.dir - User's current working directory
         java.io.tmpdir - Default temp file path -->
    <diskStore path="java.io.tmpdir"/>


    <!--Default Cache configuration. These will applied to caches programmatically created through
        the CacheManager.

        The following attributes are required for defaultCache:

        maxInMemory       - Sets the maximum number of objects that will be created in memory
        eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
                            is never expired.
        timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
                            if the element is not eternal. Idle time is now - last accessed time
        timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
                            if the element is not eternal. TTL is now - creation time
        overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
                            has reached the maxInMemory limit.

        -->
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />

    <!--Predefined caches.  Add your cache configuration settings here.
        If you do not have a configuration for your cache a WARNING will be issued when the
        CacheManager starts

        The following attributes are required for defaultCache:

        name              - Sets the name of the cache. This is used to identify the cache. It must be unique.
        maxInMemory       - Sets the maximum number of objects that will be created in memory
        eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
                            is never expired.
        timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
                            if the element is not eternal. Idle time is now - last accessed time
        timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
                            if the element is not eternal. TTL is now - creation time
        overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
                            has reached the maxInMemory limit.

        -->

    <!-- Sample cache named sampleCache1
        This cache contains a maximum in memory of 10000 elements, and will expire
        an element if it is idle for more than 5 minutes and lives for more than
        10 minutes.

        If there are more than 10000 elements it will overflow to the
        disk cache, which in this configuration will go to wherever java.io.tmp is
        defined on your system. On a standard Linux system this will be /tmp"
        -->
    <cache name="sampleCache1"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />

    <!-- Sample cache named sampleCache2
        This cache contains 1000 elements. Elements will always be held in memory.
        They are not expired. -->
    <cache name="sampleCache2"
        maxElementsInMemory="1000"
        eternal="true"
        timeToIdleSeconds="0"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        /> -->

    <!-- Place configuration for your caches following -->

</ehcache>

测试:此时,就只会发送一条sql语句

	@Test
	public void testSecondLevelCache(){
		Customer customer1 = entityManager.find(Customer.class, 1);
		
		transaction.commit();
		entityManager.close();
		
		entityManager = entityManagerFactory.createEntityManager();
		transaction = entityManager.getTransaction();
		transaction.begin();
		
		Customer customer2 = entityManager.find(Customer.class, 1);
	}

JPQL

  • JPQL语言,即 Java Persistence Query Language 的简称。JPQL 是一种和 SQL 非常类似的中间性和对象化查询语言,它最终会被编译成针对不同底层数据库的 SQL 查询,从而屏蔽不同数据库的差异。
  • JPQL语言的语句可以是 select 语句、update 语句或delete语句,它们都通过 Query 接口封装执行

Query接口

  • Query接口封装了执行数据库查询的相关方法。调用 EntityManager 的 createQuery、create NamedQuery 及 createNativeQuery 方法可以获得查询对象,进而可调用 Query 接口的相关方法来执行查询操作。
  • Query接口封装了执行数据库查询的相关方法。调用 EntityManager 的 createQuery、create NamedQuery 及 createNativeQuery 方法可以获得查询对象,进而可调用 Query 接口的相关方法来执行查询操作。
  • 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 为直到提交事务时才更新数据库记录。
    • setHint(String hintName, Object value)
      • 设置与查询对象相关的特定供应商参数或提示信息。参数名及其取值需要参考特定 JPA 实现库提供商的文档。如果第二个参数无效将抛出IllegalArgumentException异常。
    • setParameter(int position, Object value)
      • 为查询语句的指定位置参数赋值。Position 指定参数序号,value 为赋给参数的值。
    • setParameter(int position, Date d, TemporalType type)
      • 为查询语句的指定位置参数赋 Date 值。Position 指定参数序号,value 为赋给参数的值,temporalType 取 TemporalType 的枚举常量,包括 DATE、TIME 及 TIMESTAMP 三个,,用于将 Java 的 Date 型值临时转换为数据库支持的日期时间类型(java.sql.Date、java.sql.Time及java.sql.Timestamp)。
    • setParameter(int position, Calendar c, TemporalType type)
      • 为查询语句的指定位置参数赋 Calenda r值。position 指定参数序号,value 为赋给参数的值,temporalType 的含义及取舍同前。
    • setParameter(String name, Object value)
      • 为查询语句的指定名称参数赋值。
    • setParameter(String name, Date d, TemporalType type)
      • 为查询语句的指定名称参数赋 Date 值。用法同前。
    • setParameter(String name, Calendar c, TemporalType type)
      • 为查询语句的指定名称参数设置Calendar值。name为参数名,其它同前。该方法调用时如果参数位置或参数名不正确,或者所赋的参数值类型不匹配,将抛出 IllegalArgumentException 异常。

Helloworld:

	@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());
	}

获取部分属性:

	//默认情况下, 若只查询部分属性, 则将返回 Object[] 类型的结果. 或者 Object[] 类型的 List.
	//也可以在实体类中创建对应的构造器, 然后再 JPQL 语句中利用对应的构造器返回实体类的对象.
	@Test
	public void testPartlyProperties(){
		String jpql = "SELECT new Customer(c.lastName, c.age) FROM Customer c WHERE c.id > ?";
		List result = entityManager.createQuery(jpql).setParameter(1, 1).getResultList();
		
		System.out.println(result);
	}

	//createNamedQuery 适用于在实体类前使用 @NamedQuery 标记的查询语句
	@Test
	public void testNamedQuery(){
		Query query = entityManager.createNamedQuery("testNamedQuery").setParameter(1, 3);
		Customer customer = (Customer) query.getSingleResult();
		
		System.out.println(customer);
	}

	//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);
	}

select语句

  • select语句用于执行查询。其语法可表示为:
    • select_clause
    • form_clause
    • [where_clause]
    • [groupby_clause]
    • [having_clause]
    • [orderby_clause]

select-from 子句

  • from 子句是查询语句的必选子句。
    • Select 用来指定查询返回的结果实体或实体的某些属性
    • From 子句声明查询源实体类,并指定标识符变量(相当于SQL表的别名)。
  • 如果不希望返回重复实体,可使用关键字 distinct 修饰。select、from 都是 JPQL 的关键字,通常全大写或全小写,建议不要大小写混用。

查询所有实体

  • 查询所有实体的 JPQL 查询字串很简单,例如:
    select o from Order o 或 select o from Order as o
  • 关键字 as 可以省去。
  • 标识符变量的命名规范与 Java 标识符相同,且区分大小写。
  • 调用 EntityManager 的 createQuery() 方法可创建查询对象,接着调用 Query 接口的 getResultList() 方法就可获得查询结果集。例如:
Query query = entityManager.createQuery( "select o from Order o"); 
List orders = query.getResultList();
Iterator iterator = orders.iterator();
while( iterator.hasNext() ) {
	// 处理Order
}

where子句

  • where子句用于指定查询条件,where跟条件表达式。例:
    select o from Orders o where o.id = 1
    select o from Orders o where o.id > 3 and o.confirm = ‘true’
    select o from Orders o where o.address.streetNumber >= 123
  • JPQL也支持包含参数的查询,例如:
    select o from Orders o where o.id = :myId
    select o from Orders o where o.id = :myId and o.customer = :customerName
     注意:参数名前必须冠以冒号(?,执行查询前须使用Query.setParameter(name, value)方法给参数赋值。
  • 也可以不使用参数名而使用参数的序号,例如:

select o from Order o where o.id = ?1 and o.customer = ?2

其中 ?1 代表第一个参数,?2 代表第一个参数。在执行查询之前需要使用重载方法Query.setParameter(pos, value) 提供参数值。

Query query = entityManager.createQuery( "select o from  	Orders o where o.id = ?1 and o.customer = ?2" );
query.setParameter( 1, 2 );
query.setParameter( 2, "John" );
List orders = query.getResultList();
  • where条件表达式中可用的运算符基本上与SQL一致,包括:
    • 算术运算符:+ - * / +(正) -(负)
    • 关系运算符:== <> > >= < <= between…and like in is null 等
    • 逻辑运算符: and or  not

where子句示例

  • 下面是一些常见查询表达式示例:
// 以下语句查询 Id 介于 100 至 200 之间的订单。
select o from Orders o where o.id between 100 and 200
// 以下语句查询国籍为的 'US'、'CN'或'JP' 的客户。
select c from Customers c where c.county in ('US','CN','JP')
// 以下语句查询手机号以139开头的客户。%表示任意多个字符序列,包括0个。
select c from Customers c where c.phone like '139%'
// 以下语句查询名字包含4个字符,且234位为ose的客户。_表示任意单个字符。
select c from Customers c where c.lname like '_ose' 
// 以下语句查询电话号码未知的客户。Nul l用于测试单值是否为空。
select c from Customers c where c.phone is null
// 以下语句查询尚未输入订单项的订单。empty用于测试集合是否为空。
select o from Orders o where o.orderItems is empty

查询部分属性

  • 如果只须查询实体的部分属性而不需要返回整个实体。例如:

select o.id, o.customerName, o.address.streetNumber from Order o order by o.id

  • 执行该查询返回的不再是Orders实体集合,而是一个对象数组的集合(Object[]),集合的每个成员为一个对象数组,可通过数组元素访问各个属性。

查询缓存

在这里插入图片描述


	//使用 hibernate 的查询缓存. 
	@Test
	public void testQueryCache(){
		String jpql = "FROM Customer c WHERE c.age > ?";
		Query query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);
		
		//占位符的索引是从 1 开始
		query.setParameter(1, 1);
		List<Customer> customers = query.getResultList();
		System.out.println(customers.size());
		
		query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);
		
		//占位符的索引是从 1 开始
		query.setParameter(1, 1);
		customers = query.getResultList();
		System.out.println(customers.size());
	}

order by子句

  • order by子句用于对查询结果集进行排序。和SQL的用法类似,可以用 “asc“ 和 "desc“ 指定升降序。如果不显式注明,默认为升序。
select o from Orders o order by o.id
select o from Orders o order by o.address.streetNumber desc
select o from Orders o order by o.customer asc, o.id desc
	@Test
	public void testOrderBy(){
		String jpql = "FROM Customer c WHERE c.age > ? ORDER BY c.age DESC";
		Query query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);
		
		//占位符的索引是从 1 开始
		query.setParameter(1, 1);
		List<Customer> customers = query.getResultList();
		System.out.println(customers.size());
	}

group by子句与聚合查询

  • group by 子句用于对查询结果分组统计,通常需要使用聚合函数。常用的聚合函数主要有 AVG、SUM、COUNT、MAX、MIN 等,它们的含义与SQL相同。例如:
select max(o.id) from Orders o
  • 没有 group by 子句的查询是基于整个实体类的,使用聚合函数将返回单个结果值,可以使用Query.getSingleResult()得到查询结果。例如:
Query query = entityManager.createQuery(
					"select max(o.id) from Orders o");
Object result = query.getSingleResult();
Long max = (Long)result;
	//查询 order 数量大于 2 的那些 Customer
	@Test
	public void testGroupBy(){
		String jpql = "SELECT o.customer FROM Order o "
				+ "GROUP BY o.customer "
				+ "HAVING count(o.id) >= 2";
		List<Customer> customers = entityManager.createQuery(jpql).getResultList();
		
		System.out.println(customers);
	}

关联查询

  • 在JPQL中,很多时候都是通过在实体类中配置实体关联的类属性来实现隐含的关联(join)查询。例如:
    select o from Orders o where o.address.streetNumber=2000
  • 上述JPQL语句编译成以下SQL时就会自动包含关联,默认为左关联。
  • 在某些情况下可能仍然需要对关联做精确的控制。为此,JPQL 也支持和 SQL 中类似的关联语法。如:
    • left out join / left join
    • inner join
    • left join / inner join fetch
    • 其中,left join和left out join等义,都是允许符合条件的右边表达式中的实体为空。
  • 例如,以下外关联查询可以找出所有客户实体记录,即使它未曾订货:
    select c from Customers c left join c.orders o
  • 以下内关联查询只找出所有曾订过商品的客户实体记录:
    select c from Customers c inner join c.orders o
  • 如果001号客户下过5次订单的话,以下fetch关联查询将得到 5个客户实体的引用,并且执行了 5 个订单的查询:
    select c from Customers c left join fetch c.orders o where c.id=001
	/**
	 * JPQL 的关联查询同 HQL 的关联查询. 
	 */
	@Test
	public void testLeftOuterJoinFetch(){
		String jpql = "FROM Customer c LEFT OUTER JOIN FETCH c.orders WHERE c.id = ?";
		
		Customer customer = 
				(Customer) entityManager.createQuery(jpql).setParameter(1, 12).getSingleResult();
		System.out.println(customer.getLastName());
		System.out.println(customer.getOrders().size());
		
//		List<Object[]> result = entityManager.createQuery(jpql).setParameter(1, 12).getResultList();
//		System.out.println(result);
	}

子查询

  • JPQL也支持子查询,在 where 或 having 子句中可以包含另一个查询。当子查询返回多于 1 个结果集时,它常出现在 any、all、exist s表达式中用于集合匹配查询。它们的用法与SQL语句基本相同。
	@Test
	public void testSubQuery(){
		//查询所有 Customer 的 lastName 为 YY 的 Order
		String jpql = "SELECT o FROM Order o "
				+ "WHERE o.customer = (SELECT c FROM Customer c WHERE c.lastName = ?)";
		
		Query query = entityManager.createQuery(jpql).setParameter(1, "YY");
		List<Order> orders = query.getResultList();
		System.out.println(orders.size());
	}

JPQL函数

  • JPQL提供了以下一些内建函数,包括字符串处理函数、算术函数和日期函数。
  • 字符串处理函数主要有:
    • concat(String s1, String s2):字符串合并/连接函数。
    • substring(String s, int start, int length):取字串函数。
    • trim([leading|trailing|both,] [char c,] String s):从字符串中去掉首/尾指定的字符或空格。
    • lower(String s):将字符串转换成小写形式。
    • upper(String s):将字符串转换成大写形式。
    • length(String s):求字符串的长度。
    • locate(String s1, String s2[, int start]):从第一个字符串中查找第二个字符串(子串)出现的位置。若未找到则返回0。
	//使用 jpql 内建的函数
	@Test
	public void testJpqlFunction(){
		String jpql = "SELECT lower(c.email) FROM Customer c";
		
		List<String> emails = entityManager.createQuery(jpql).getResultList();
		System.out.println(emails);
	}

update语句

  • update语句用于执行数据更新操作。主要用于针对单个实体类的批量更新
  • 以下语句将帐户余额不足万元的客户状态设置为未偿付:
update Customers c set c.status = '未偿付' where c.balance < 10000

delete语句

  • delete语句用于执行数据更新操作。
  • 以下语句删除不活跃的、没有订单的客户:
delete from Customers c where c.status = 'inactive' and c.orders is empty
	//可以使用 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();
	}

Spring 整合 JPA

  • 三种整合方式:
    • LocalEntityManagerFactoryBean:适用于那些仅使用 JPA 进行数据访问的项目,该 FactoryBean 将根据JPA PersistenceProvider 自动检测 配置文件进行工作,一般从“META-INF/persistence.xml”读取配置信息,这种方式最简单,但不能设置 Spring 中定义的DataSource,且不支持 Spring 管理的全局事务
    • 从JNDI中获取:用于从 Java EE 服务器获取指定的EntityManagerFactory,这种方式在进行 Spring 事务管理时一般要使用 JTA 事务管理
    • LocalContainerEntityManagerFactoryBean:适用于所有环境的 FactoryBean,能全面控制 EntityManagerFactory 配置,如指定 Spring 定义的 DataSource 等等。
<!-- 配置 JPA 提供者的适配器 -->
<bean id="jpaVendorAdapter"  	class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
	<property name="databasePlatform">
		<bean class="com.atguigu.ssps.modules.persistence.Hibernates" 
			factory-method="getDialect">
				<constructor-arg ref="dataSource"></constructor-arg>
		</bean>
	</property>
</bean>

<!-- 配置 JPA 的 EntityManager -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
	<property name="dataSource" ref="dataSource"></property>
	<property name="jpaVendorAdapter" ref="jpaVendorAdapter"></property>
	<property name="packagesToScan" value="com.atuigu.crm"></property>
	<property name="jpaProperties">
		<props>
			<!-- 二级缓存相关 -->
			<prop key="hibernate.cache.region.factory_class">
				org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
			<prop key="net.sf.ehcache.configurationResourceName">
				ehcache-hibernate.xml</prop>
			<!-- 生成的数据表的列的映射策略 -->
			<prop key="hibernate.ejb.naming_strategy">
				org.hibernate.cfg.ImprovedNamingStrategy</prop>
			<!-- hibernate 基本属性 -->
			<prop key="hibernate.show_sql">true</prop>
			<prop key="hibernate.format_sql">true</prop>
			<prop key="hibernate.hbm2ddl.auto">update</prop>
		</props>
	</property>
</bean>

导入相关的jar包:
在这里插入图片描述


jdbc.user=root
jdbc.password=12358
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql:///jpa

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

	<!-- 配置自动扫描的包 -->
	<context:component-scan base-package="com.atguigu.jpa"></context:component-scan>

	<!-- 配置 C3P0 数据源 -->
	<context:property-placeholder location="classpath:db.properties"/>

	<bean id="dataSource"
		class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="user" value="${jdbc.user}"></property>
		<property name="password" value="${jdbc.password}"></property>
		<property name="driverClass" value="${jdbc.driverClass}"></property>
		<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>	
		
		<!-- 配置其他属性 -->
	</bean>
	
</beans>

我们来对数据源进行测试一下:

public class JPATest {
	
	
	private ApplicationContext ctx = null;
	
	{
		ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
	}

	@Test
	public void testDataSource() throws SQLException {
		DataSource dataSource = ctx.getBean(DataSource.class);
		System.out.println(dataSource.getConnection());
	}

}

在这里插入图片描述


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

	<!-- 配置自动扫描的包 -->
	<context:component-scan base-package="com.atguigu.jpa"></context:component-scan>

	<!-- 配置 C3P0 数据源 -->
	<context:property-placeholder location="classpath:db.properties"/>

	<bean id="dataSource"
		class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="user" value="${jdbc.user}"></property>
		<property name="password" value="${jdbc.password}"></property>
		<property name="driverClass" value="${jdbc.driverClass}"></property>
		<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>	
		
		<!-- 配置其他属性 -->
	</bean>
	
	<!-- 配置 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>
	
	<!-- 配置 JPA 使用的事务管理器 -->
	<bean id="transactionManager"
		class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory"></property>	
	</bean>
	
	<!-- 配置支持基于注解是事务配置 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

Person实体类:

@Table(name="JPA_PERSONS")
@Entity
public class Person {

	private Integer id;
	private String lastName;

	private String email;
	private int age;

	@GeneratedValue
	@Id
	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	@Column(name="LAST_NAME")
	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}

PersonDao 类:

@Repository
public class PersonDao {

	//如何获取到和当前事务关联的 EntityManager 对象呢 ?
	//通过 @PersistenceContext 注解来标记成员变量!
	@PersistenceContext
	private EntityManager entityManager;
	
	public void save(Person person){
		entityManager.persist(person);
	}
	
}

PersonService 类

@Service
public class PersonService {
	
	@Autowired
	private PersonDao personDao;
	
	@Transactional
	public void savePersons(Person p1, Person p2){
		personDao.save(p1);
		
		int i = 10 / 0;
		
		personDao.save(p2);
	}
	
}

对Service进行测试:

	@Test
	public void testPersonService(){
		Person p1 = new Person();
		p1.setAge(11);
		p1.setEmail("aa@163.com");
		p1.setLastName("AA");
		
		Person p2 = new Person();
		p2.setAge(12);
		p2.setEmail("bb@163.com");
		p2.setLastName("BB");
		
		System.out.println(personService.getClass().getName());
		personService.savePersons(p1, p2);
	}
<think>我们正在讨论JPA中的多表关联查询,特别是张表关联时返回对象的格式。根据用户之前的问题和引用[1]的内容,用户似乎对对象导航查询感兴趣,即在实体中通过关联属性获取关联对象的数据。 在JPA中,处理多表关联主要有两种方式: 1. 对象导航查询:利用实体类中定义的关联关系(如@OneToMany, @ManyToOne等)进行级联查询。 2. 使用JPQL或原生SQL进行连接查询,并返回一个包含多个表数据的对象(可能是自定义的DTO或Object数组)。 针对张表关联,返回对象的格式取决于查询方式: ### 1. 对象导航查询 如果通过定义好的实体关联关系进行导航查询,那么返回的对象是一个实体对象,该实体对象中包含关联实体或实体集合。 例如:有`Class`(班级)、`Student`(学生)和`Course`(课程)张表,假设关系如下: - 一个班级有多个学生(Class 1->* Student) - 一个学生选修多门课程(Student 1->* Course) 那么,在`Class`实体中可能定义: ```java @Entity public class Class { @Id private Long id; private String className; @OneToMany(mappedBy = "class") private Set<Student> students; // getters and setters } @Entity public class Student { @Id private Long id; private String name; @ManyToOne @JoinColumn(name = "class_id") private Class class; @OneToMany(mappedBy = "student") private Set<Course> courses; // getters and setters } @Entity public class Course { @Id private Long id; private String courseName; @ManyToOne @JoinColumn(name = "student_id") private Student student; // getters and setters } ``` 当我们查询一个班级时,可以通过班级对象导航到学生,再通过学生导航到课程: ```java Class myClass = entityManager.find(Class.class, classId); Set<Student> students = myClass.getStudents(); // 获取班级的学生集合 for(Student student : students) { Set<Course> courses = student.getCourses(); // 获取每个学生的课程集合 // ... } ``` 返回的对象格式是嵌套的实体对象。这种方式可能会产生N+1查询问题,可以通过设置FetchType.EAGER或使用JOIN FETCH来优化。 ### 2. 使用JPQL连接查询返回自定义结果 如果我们希望一次查询获取多个表的数据,可以使用JPQL的连接查询,并返回一个自定义的DTO(Data Transfer Object)或Object数组。 例如,查询班级、学生和课程的信息: ```java String jpql = "SELECT c, s, co FROM Class c JOIN c.students s JOIN s.courses co WHERE c.id = :classId"; List<Object[]> resultList = entityManager.createQuery(jpql, Object[].class) .setParameter("classId", classId) .getResultList(); for (Object[] row : resultList) { Class c = (Class) row[0]; Student s = (Student) row[1]; Course co = (Course) row[2]; // 处理每一行数据 } ``` 或者,使用构造函数表达式返回DTO: ```java public class ClassStudentCourseDTO { private String className; private String studentName; private String courseName; public ClassStudentCourseDTO(String className, String studentName, String courseName) { this.className = className; this.studentName = studentName; this.courseName = courseName; } // getters and setters } String jpql = "SELECT NEW com.example.ClassStudentCourseDTO(c.className, s.name, co.courseName) FROM Class c JOIN c.students s JOIN s.courses co WHERE c.id = :classId"; List<ClassStudentCourseDTO> resultList = entityManager.createQuery(jpql, ClassStudentCourseDTO.class) .setParameter("classId", classId) .getResultList(); ``` 这种方式返回的是我们自定义的DTO对象列表,每个DTO包含来自张表的字段。 ### 总结 - **对象导航查询**:返回的是完整的实体对象,包含关联的实体集合(懒加载或急加载),格式是嵌套的对象结构。 - **JPQL连接查询**:可以返回Object[]数组(数组中的元素是查询的实体或字段)或自定义的DTO对象,格式更扁平化,可以精确控制返回的数据。 根据实际需求选择合适的方式。如果只需要部分数据,推荐使用DTO投影以避免不必要的数据传输和性能问题;如果需要完整的对象图,则可以使用对象导航(注意优化查询避免N+1问题)。 §§相关问题§§ 1. JPA中如何解决对象导航查询的N+1问题? 2. JPA的DTO投影和实体投影有什么区别? 3. 在JPA中如何进行张表以上的关联查询? 4. JPA的@OneToMany和@ManyToOne注解如何配置? </think> ### JPA 张表关联返回对象的格式 在 JPA 中处理张表关联查询时,返回对象的格式主要取决于**关联关系的配置方式**和**查询方法的选择**。以下是两种典型场景的返回格式: --- ### 1. 对象导航方式(基于实体关联) 通过实体间的关联关系(`@OneToMany`/`@ManyToOne`)自动级联查询,返回**嵌套对象结构**。 **示例场景**:部门(Department)→ 员工(Employee)→ 项目(Project) ```java @Entity public class Department { @Id private Long id; private String name; @OneToMany(mappedBy = "department") // 一对多关联员工 private Set<Employee> employees; // 嵌套集合 } @Entity public class Employee { @Id private Long id; private String name; @ManyToOne @JoinColumn(name = "dept_id") private Department department; // 多对一关联部门 @OneToMany(mappedBy = "employee") // 一对多关联项目 private Set<Project> projects; // 嵌套集合 } @Entity public class Project { @Id private Long id; private String projectName; @ManyToOne @JoinColumn(name = "emp_id") private Employee employee; // 多对一关联员工 } ``` **查询与返回格式**: ```java Department dept = entityManager.find(Department.class, 1L); // 返回的嵌套对象结构: { "id": 1, "name": "研发部", "employees": [ { "id": 101, "name": "张", "projects": [ {"id": 1001, "projectName": "项目A"}, {"id": 1002, "projectName": "项目B"} ] }, { "id": 102, "name": "李四", "projects": [ {"id": 1003, "projectName": "项目C"} ] } ] } ``` > ⚠️ **注意**:默认懒加载(Lazy Loading),访问嵌套集合时会触发额外查询。可通过 `@OneToMany(fetch = FetchType.EAGER)` 改为急加载[^1]。 --- ### 2. JPQL 或原生 SQL 连接查询 使用显式连接查询,返回**扁平化结构**(如 `Object[]` 或 DTO 对象)。 **JPQL 示例**: ```java String jpql = """ SELECT d.name AS deptName, e.name AS empName, p.projectName FROM Department d JOIN d.employees e JOIN e.projects p WHERE d.id = :deptId """; List<Object[]> results = entityManager .createQuery(jpql, Object[].class) .setParameter("deptId", 1L) .getResultList(); // 返回格式:每行一个Object数组 // [0]=部门名, [1]=员工名, [2]=项目名 for (Object[] row : results) { System.out.println(row[0] + " | " + row[1] + " | " + row[2]); } ``` 输出: ``` 研发部 | 张 | 项目A 研发部 | 张 | 项目B 研发部 | 李四 | 项目C ``` **DTO 投影(推荐)**: 定义自定义类接收结果: ```java public class DeptEmpProjectDTO { private String deptName; private String empName; private String projectName; // 构造方法 + getters } List<DeptEmpProjectDTO> results = entityManager .createQuery(""" SELECT NEW com.example.DeptEmpProjectDTO( d.name, e.name, p.projectName ) FROM Department d JOIN d.employees e JOIN e.projects p WHERE d.id = :deptId """, DeptEmpProjectDTO.class) .setParameter("deptId", 1L) .getResultList(); ``` --- ### 关键差异总结 | **查询方式** | 返回格式 | 适用场景 | |---------------------|------------------------|----------------------------| | 对象导航 | 嵌套对象(树形结构) | 需要完整实体图的场景 | | JPQL/SQL 连接查询 | 扁平化数据(行列结构) | 只需部分字段或聚合计算 | > 💡 **建议**: > - 优先使用 DTO 投影避免不必要的数据加载 > - 多对多关联需通过 `@ManyToMany` 配置中间实体 > - 复杂查询可用 `@Query` 注解定制 JPQL 或原生 SQL
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值