JPA基础知识02----映射关联关系、二级缓存、JPQL语言

一、映射关联关系

1-1. 映射单向多对一的关联关系

多个Order对应一个Customer。外键在多的一端

	//映射单向 n-1 的关联关系,在多的一端写注解
	//使用 @ManyToOne 来映射多对一的关联关系
	//使用 @JoinColumn 来映射外键.外键命名随意 (会在多的那张表上生成一列CUSTOMER_ID)
	//可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
	@JoinColumn(name="CUSTOMER_ID")
	@ManyToOne(fetch=FetchType.LAZY) //如果用懒加载,则会有两条select语句,先查order,再customer
	public Customer1 getCustomer1() {
		return customer1;
	}
	public void setCustomer1(Customer1 customer1) {
		this.customer1 = customer1;
	}
测试
public class ManyToOneTest {
	private EntityManagerFactory entityManagerFactory;
	private EntityManager entityManager;
	private EntityTransaction transaction;
	
	@Before
	public void init(){
		entityManagerFactory = Persistence.createEntityManagerFactory("jpa-1");
		entityManager = entityManagerFactory.createEntityManager();
		transaction = entityManager.getTransaction();
		transaction.begin();
	}
	
	@After
	public void destroy(){
		transaction.commit();
		entityManager.close();
		entityManagerFactory.close();
	}
	
	@Test
	public void testManyToOneUpdate(){
		Order1 order = entityManager.find(Order1.class, 2);
		order.getCustomer1().setLastName("FFF");
	}
	
	//不能直接删除 1 的一端, 因为有外键约束. 
	@Test
	public void testManyToOneRemove(){
//		Order order = entityManager.find(Order.class, 1);
//		entityManager.remove(order);
		
		Customer1 customer = entityManager.find(Customer1.class, 7);
		entityManager.remove(customer); //会有异常
	}
	
	//默认情况下, 使用左外连接的方式来获取 n 的一端的对象和其关联的 1 的一端的对象(只有一条查询语句)
	//可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略。如果用懒加载,两条select语句,先查order,再查customer
	@Test
	public void testManyToOneFind(){
		Order1 order = entityManager.find(Order1.class, 1);
		System.out.println(order.getOrderName());
		
		//sql语句显示:默认是左外链接的方式
		System.out.println(order.getCustomer1().getLastName());
	}

	
	/**
	 * 保存多对一时, 建议先保存 1 的一端, 后保存 n 的一端, 这样不会多出额外的 UPDATE 语句.
	 */
	@Test
	public void testManyToOnePersist(){
		Customer1 customer = new Customer1();
		customer.setAge(18);
		customer.setBirth(new Date());
		customer.setCreatedTime(new Date());
		customer.setEmail("gg@163.com");
		customer.setLastName("GG");
		
		Order1 order1 = new Order1();
		order1.setOrderName("G-GG-1");
		Order1 order2 = new Order1();
		order2.setOrderName("G-GG-2");
		
		//设置关联关系
		order1.setCustomer1(customer);
		order2.setCustomer1(customer);
		
		//执行保存操作:先保存多的那一端,3条insert语句,多了两条Update语句
/*		entityManager.persist(order1);
		entityManager.persist(order2);
		
		entityManager.persist(customer);*/
		
		//先保存一的那一端,3条insert语句
		entityManager.persist(customer);
		
		entityManager.persist(order1);
		entityManager.persist(order2);
	}

}
1-2、单向一对多的关联关系

一个Customer对应多个Orders,也是外键在多的一端!!!

	//映射单向 1-n 的关联关系
	//使用 @OneToMany 来映射 1-n 的关联关系
	//使用 @JoinColumn 来映射外键列的名称(在多的那张表JPA_ORDERS!!!!生成一列CUSTOMER_ID
	//可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略,如果是eager,则输出的sql语句是左外连接
	//可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略. remove会在删除一的一端的同时,也会把多的一端删除
	//注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了. 
	@JoinColumn(name="CUSTOMER_ID")
	@OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.REMOVE})
	public Set<Order2> getOrders() {
		return orders;
	}
	public void setOrders(Set<Order2> orders) {
		this.orders = orders;
	}
测试
	//默认情况下, 若删除 1 的一端, 则会先把关联的 n 的一端的外键置空, 然后进行删除. 
	//可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略. 
	@Test
	public void testOneToManyRemove(){
		Customer2 customer = entityManager.find(Customer2.class, 8);
		entityManager.remove(customer);
	}
	
	//默认对关联的多的一方使用懒加载的加载策略. 即先打印customer,后打印order,没有用左外连接
	//可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略
	@Test
	public void testOneToManyFind(){
		Customer2 customer = entityManager.find(Customer2.class, 9);
		System.out.println(customer.getLastName());
		
		System.out.println(customer.getOrders().size());
	}
	
	//若是双向 1-n 的关联关系, 执行保存时
	//若先保存 n 的一端, 再保存 1 的一端, 默认情况下, 会多出 n 条 UPDATE 语句.
	//若先保存 1 的一端, 则会多出 n 条 UPDATE 语句
	//在进行双向 1-n 关联关系时, 建议使用 n 的一方来维护关联关系, 而 1 的一方不维护关联系, 这样会有效的减少 SQL 语句. 
	//注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了. 
	
	//单向 1-n 关联关系执行保存时, 一定会多出 UPDATE 语句.
	//因为 n 的一端在插入时不会同时插入外键列. 
	@Test
	public void testOneToManyPersist(){
		Customer2 customer = new Customer2();
		customer.setAge(18);
		customer.setBirth(new Date());
		customer.setCreatedTime(new Date());
		customer.setEmail("mm@163.com");
		customer.setLastName("MM");
		
		Order2 order1 = new Order2();
		order1.setOrderName("O-MM-1");
		Order2 order2 = new Order2();
		order2.setOrderName("O-MM-2");
		
		//建立关联关系
		customer.getOrders().add(order1);
		customer.getOrders().add(order2);
		
		//不管哪保存哪一端,都有3条insert, 2条update
		entityManager.persist(customer);

		entityManager.persist(order1);
		entityManager.persist(order2);
	}

1-3.双向多对一的关联关系

上面两个结合就行,需要注意的是两个 JoinColum里的name要一样

	//映射单向 1-n 的关联关系
	//使用 @OneToMany 来映射 1-n 的关联关系
	//使用 @JoinColumn 来映射外键列的名称(在多的那张表JPA_ORDERS!!!!生成一列CUSTOMER_ID
	//可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略,如果是eager,则输出的sql语句是左外连接
	
	//可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略. remove会在删除一的一端的同时,也会把多的一端删除
	//注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了.
	//并且mappedBy会让一的这一端放弃维护,而由多的一端维护(mappedBy里的会值是Order3里的字段customer3)
//	@JoinColumn(name="CUSTOMER_ID")  //用了mappedBy就不用JoinColumn
	@OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.REMOVE}, mappedBy="customer3")
	public Set<Order3> getOrders() {
		return orders;
	}
	public void setOrders(Set<Order3> orders) {
		this.orders = orders;
	}

	//若是双向 1-n 的关联关系, 执行保存时
	//若先保存 n 的一端, 再保存 1 的一端, 默认情况下, 会多出4 条 UPDATE 语句.
	//若先保存 1 的一端, 则会多出 2 条 UPDATE 语句

	//在进行双向 1-n 关联关系时, 建议使用 n 的一方来维护关联关系, 而 1 的一方不维护关联系(也就是先保存1 的一端), 这样会有效的减少 SQL 语句.
	//注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了. 
	
	//单向 1-n 关联关系执行保存时, 一定会多出 UPDATE 语句.
	//因为 n 的一端在插入时不会同时插入外键列. 

1-4.映射双向一对一的关联关系

Department类

	/**
	 * 基于外键
	 * 使用 @OneToOne 来映射 1-1 关联关系。外键在这张表生成 
	 * 若需要在当前数据表中添加主键则需要使用 @JoinColumn 来进行映射. 注意, 1-1 关联关系, 所以需要添加 unique=true
	 */
	@JoinColumn(name="MGR_ID", unique=true) //在Department表中创建一列作外键,必须有唯一约束,表示由该表维护
	@OneToOne(fetch=FetchType.LAZY) //我们不希望通过左外连接获取其关联的对象,可以设置为懒加载
	public Manager getMgr() {
		return mgr;
	}
	public void setMgr(Manager mgr) {
		this.mgr = mgr;
	}
Manager类
	/**
	 * 基于外键,由对方维护关联关系
	 * 
	 * 对于(不维护!!!)关联关系, 没有外键的一方,
	 *  使用 @OneToOne 来进行映射, 建议设置 mappedBy=true
	 */
	@OneToOne(mappedBy="mgr") //这里的值是Department里的字段 private Manager mgr;
	public Department getDept() {
		return dept;
	}
	public void setDept(Department dept) {
		this.dept = dept;
	}
测试

	//1. 默认情况下, 若获取不维护关联关系的一方, 则也会通过左外连接获取其关联的对象. 
	//可以通过 @OneToOne 的 fetch 属性来修改加载策略. 但依然会再发送 SQL 语句来初始化其关联的对象
	//这说明在不维护关联关系的一方, 不建议修改 fetch 属性. 
	@Test
	public void testOneToOneFind2(){
		Manager mgr = entityManager.find(Manager.class, 1);
		System.out.println(mgr.getMgrName());
		
		System.out.println(mgr.getDept().getClass().getName());
	}
	
	//1.默认情况下, 若获取维护关联关系的一方, 则会通过左外连接获取其关联的对象. 
	//但可以通过 @OntToOne 的 fetch 属性来修改加载策略.
	@Test
	public void testOneToOneFind(){
		Department dept = entityManager.find(Department.class, 1);
		System.out.println(dept.getDeptName());
		System.out.println(dept.getMgr().getClass().getName());
	}
	
	//双向 1-1 的关联关系, 建议先保存不维护关联关系的一方, 即没有外键的一方, 这样不会多出 UPDATE 语句.
	@Test
	public void testOneToOnePersistence(){
		Manager mgr = new Manager();
		mgr.setMgrName("M-BB");
		
		Department dept = new Department();
		dept.setDeptName("D-BB");
		
		//设置关联关系
		mgr.setDept(dept);
		dept.setMgr(mgr);
		
		//先保存(不维护!!!)关联关系的一方,2条Insert
		entityManager.persist(mgr);
		entityManager.persist(dept);
		
		//先保存(维护!!!)关联关系的一方,2条Insert,1条update
		//entityManager.persist(dept);
		//entityManager.persist(mgr);
	}

1-5.映射双向多对的关联关系

多对多需要有一个中间表

item类

	//使用 @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")}, //(ID当前表的主键)
			inverseJoinColumns={@JoinColumn(name="CATEGORY_ID", referencedColumnName="ID")}) //ID是Category表中的主键
	@ManyToMany
	public Set<Category> getCategories() {
		return categories;
	}
	public void setCategories(Set<Category> categories) {
		this.categories = categories;
	}
Category类
	//mappedBy是Item类里的private Set<Category> categories
	@ManyToMany(mappedBy="categories")
	public Set<Item> getItems() {
		return items;
	}
	public void setItems(Set<Item> items) {
		this.items = items;
	}
测试
	//对于关联的集合对象, 默认使用懒加载的策略.
	//使用维护关联关系的一方获取, 还是使用不维护关联关系的一方获取, SQL 语句相同. 
	@Test
	public void testManyToManyFind(){
//		Item item = entityManager.find(Item.class, 5);
//		System.out.println(item.getItemName());
//		
//		System.out.println(item.getCategories().size());
		
		Category category = entityManager.find(Category.class, 3);
		System.out.println(category.getCategoryName());
		System.out.println(category.getItems().size());
	}
	
二、使用二级缓存
在persistence.xml里配置

		<!-- 
		配置二级缓存的策略 
		ALL:所有的实体类都被缓存
		NONE:所有的实体类都不被缓存. 
		ENABLE_SELECTIVE:标识 @Cacheable(true) 注解的实体类将被缓存
		DISABLE_SELECTIVE:缓存除标识 @Cacheable(false) 以外的所有实体类
		UNSPECIFIED:默认值,JPA 产品默认值将被使用
		-->
		<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>

三、JPQL语言

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

3-1、javax.persistence.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 异常。

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

查询所有实体的 JPQL 查询字串很简单,例如:
    select o from Order o 或  select o from Order as o
关键字 as 可以省去。
标识符变量的命名规范与 Java 标识符相同,且区分大小写。
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。

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

		// Customer需要有对应的构造器
		String jpql = "SELECT new Customer(c.lastName, c.age) FROM Customer c WHERE c.id > ?";
		List result = entityManager.createQuery(jpql).setParameter(1, 10).getResultList();
		
		System.out.println(result);
	}
	
	@Test
	public void testHelloJPQL(){
		String jpql = "FROM Customer c WHERE c.age > ?";
		Query query = entityManager.createQuery(jpql);
		
		//占位符的索引是从 1 开始,?对应 10
		query.setParameter(1, 10);
		List<Customer> customers = query.getResultList();
		System.out.println(customers.size());
	}
测试NameQuery
@NamedQuery(name="testNamedQuery", query="select c FROM Customer4 c WHERE c.id = ?")  // 这里必须要有select
@Table(name="JPA_CUTOMERS")
@Entity
public class Customer4 {
...
}
	//createNamedQuery 适用于在实体类前使用 @NamedQuery 标记的查询语句
	@Test
	public void testNamedQuery(){
		//这个查询的注解在Customer类上面
		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);
	}
-------------------------------------------------------------------------------------------------------------------------------------------
使用 Hibernate 的查询缓存
	//使用 hibernate 的查询缓存. 默认两条select语句,
	//如果设置setHint只一条select语句,前提在配置文件里配置了二级缓存
	//<property name="hibernate.cache.use_query_cache" value="true"/>
	@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());
	}

	/**
	 * JPQL 的关联查询同 HQL 的关联查询. 
	 */
	@Test
	public void testLeftOuterJoinFetch(){
		//使用LEFT OUTER一条语句获取两个关联对象的内容 。 FETCH去除会出错,最好 加上,因为得到的是一个对象
		String jpql = "FROM Customer4 c LEFT OUTER JOIN FETCH c.orders WHERE c.id = ?";
		
		Customer4 customer = 
				(Customer4) 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 完成 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();
	}

	//使用 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);
	}
	
	//子查询
	@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<Order4> orders = query.getResultList();
		System.out.println(orders.size());
	}
	





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值