[ SSH框架 ] Hibernate框架学习之四(JPA)
Posted Kevin_Zhang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[ SSH框架 ] Hibernate框架学习之四(JPA)相关的知识,希望对你有一定的参考价值。
一、JPA概述以及它和Hibernate之间的关系
1.1.Hibernate 概述
JPA Java Persistence API,是EJB3规范中负责对象持久化的应用程序编程接口(ORM接口),它定义一系列的注释。这些注释大体可分为:类级别注释、方法级别注释、字段级别注释。给实体类添加适当的注释可以在程序运行时告诉Hibernate如何将一个实体类保存到数据库中以及如何将数据以对象的形式从数据库中读取出来。
目前有两种注释方案可以确定对象与表格之间的对应关系:一种是注释实体类的属性字段(字段级别注释),成为字段访问方式(field access mode);另一种是注释实体类的属性访问方法(方法级别注释),称为属性访问方式(property access mode)。
1.2 JPA与Hibernate 的区别
JPA和Hibernate之间的关系,可以简单的理解为JPA是标准接口,Hibernate是实现。
那么Hibernate是如何实现与JPA的这种关系的呢。Hibernate主要是通过三个组件来实现的,及hibernate-annotation、hibernate-entitymanager和hibernate-core。
- hibernate-annotation:是Hibernate支持annotation方式配置的基础,它包括了标准的JPA annotation以及Hibernate自身特殊功能的annotation。
- hibernate-core:是Hibernate的核心实现,提供了Hibernate所有的核心功能。
- hibernate-entitymanager:实现了标准的JPA,可以把它看成hibernate-core和JPA之间的适配器,它并不直接提供ORM的功能,而是对hibernate-core进行封装,使得Hibernate符合JPA的规范。
二、JPA的环境搭建
2.1 主要配置文件
使用JPA可以省去配置每个实体类的.xml 文件,只需直接在实体类中用注解的方式直接说明即可。先在src目录下的META-INF文件下创建persistence.xml配置文件,如下图所示:
persistence.xml配置内容如下图所示:
<?xml version="1.0" encoding="UTF-8"?> <persistence 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" version="2.0"> <!-- 配置持久化单元 可以制定多个持久化单元,但名称不可重复,name用于指定持久化单元名称 transaction-type:指定事务的类型 RESCOURCE_LOCAL:指的是本地代码事务 --> <persistence-unit name="myJPAUnit" transaction-type="RESOURCE_LOCAL"> <!-- JPA规范的提供商 可以不写 --> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <!-- 指定由JPA注解的实体类位置 可以不写--> <class>com.Kevin.domain.Customer</class> <!-- 连接数据库相关的一些配置,都是Hibernate的,所以只需要把之前Hibernate配置文件中的内容拷贝过来即可 --> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.mysqlDialect"/> <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/> <property name="hibernate.connection.url" value="jdbc:mysql:///hibernateday4"/> <property name="hibernate.connection.username" value="root"/> <property name="hibernate.connection.password" value="admin"/> <!-- Hibernate显示SQL语句 --> <property name="hibernate.show_sql" value="true"/> <!-- Hibernate格式化SQL语句 --> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.hbm2ddl.auto" value="update"/> <property name="hibernate.connection.provider_class" value="org.hibernate.connection.C3P0ConnectionProvider"/> </properties> </persistence-unit> </persistence>
获取JPA操作数据库的对象
在src目录下com.Kevin.utils包中创建JPAUtil类用来获取数据库操作对象,文件目录如下图:
获取方式如下:
package com.Kevin.utils; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; /** * JPA的工具类 * @author Kevin * */ public class JPAUtil { //相当于SessionFactory private static EntityManagerFactory factory; private static ThreadLocal<EntityManager> tl; static{ factory=Persistence.createEntityManagerFactory("myJPAUnit"); //名称要与persistence.xml文件中的持久化单元name一致 tl=new ThreadLocal<EntityManager>(); } /** * 获取JPA操作数据库的对象 */ // public static EntityManager createEntityManager(){ // return factory.createEntityManager(); // } /** * 获取EntityManager对象 * @return */ public static EntityManager createEntityManager(){ //从当前线程上获取EntityManager对象 EntityManager em=tl.get(); if(em==null){ em=factory.createEntityManager(); tl.set(em); } return tl.get(); } public static void main(String[]args){ createEntityManager(); } }
2.2 基本映射注释简介
2.2.1 @Entity:映射实体类(必须)
@Entity(name="EntityName") ,name 为可选 , 对应数据库中一的个表。
2.2.2 @Table:映射数据库表名(可选)
@Table(name="",catalog="",schema="") , 通常和 @Entity 配合使用 , 只能标注在实体的 class 定义处 , 表示实体对应的数据库表的信息。
name: 可选 , 表示表的名称 . 默认地 , 表名和实体名称一致 , 只有在不一致的情况下才需要指定表名。
catalog: 可选 , 表示 Catalog 名称 , 默认为 Catalog(""). schema: 可选 , 表示 Schema 名称 , 默认为 Schema(“”)。
2.2.3 @Id:映射生成主键(必选)
@id 定义了映射到数据库表的主键的属性 , 一个实体只能有一个属性被映射为主键 。置于 getXxxx() 前 。
@GeneratedValue(strategy=GenerationType,generator="") 可选 strategy: 表示主键生成策略 , 有 AUTO,INDENTITY,SEQUENCE 和 TABLE 4 种 , 分别表示让 ORM 框架自动选择。根据数据库的 Identity 字段生成 , 根据数据库表的 Sequence 字段生成 , 以有根据一个额外的表生成主键 , 默认为 AUTO 。
generator: 表示主键生成器的名称 , 这个属性通常和 ORM 框架相关 , 例如 ,Hibernate 可以指定 uuid 等主键生成方式。
2.2.4 @Column:映射表格列(可选)
@Column 描述了数据库表中该字段的详细定义 , 这对于根据 JPA 注解生成数据库表结构的工具非常有作用 。
name: 表示数据库表中该字段的名称 , 默认情形属性名称一致 。nullable: 表示该字段是否允许为 null, 默认为 true。
unique: 表示该字段是否是唯一标识 , 默认为 false length: 表示该字段的大小 , 仅对 String 类型的字段有效。
insertable: 表示在 ORM 框架执行插入操作时 , 该字段是否应出现 INSETRT 语句中 , 默认为 true。
updateable: 表示在 ORM 框架执行更新操作时 , 该字段是否应该出现在 UPDATE 语句中 , 默认为 true. 对于一经创建就不可以更改的字段 , 该属性非常有用 , 如对于 birthday 字段 。
columnDefinition: 表示该字段在数据库中的实际类型 . 通常 ORM 框架可以根据属性类型自动判断数据库中字段的类型 , 但是对于 Date 类型仍无法确定数据库中字段类型究竟是 DATE,TIME 还是 TIMESTAMP. 此外 ,String 的默认映射类型为 VARCHAR, 如果要将 String 类型映射到特定数据库的 BLOB 或 TEXT 字段类型 , 该属性非常有用 .。
2.2.5 @Transient:定义暂态属性(可选)
@Transient 表示该属性并非一个到数据库表的字段的映射 ,ORM 框架将忽略该属性。如果一个属性并非数据库表的字段映射 , 就务必将其标示为 @Transient, 否则 ,ORM 框架默认其注解为 @Basic。
2.3 关联类映射注释简介
2.3.1 @ManyToOne(可选)
@ManyToOne(fetch=FetchType,cascade=CascadeType )
@ManyToOne 表示一个多对一的映射 , 该注解标注的属性通常是数据库表的外键 。
optional: 是否允许该字段为 null, 该属性应该根据数据库表的外键约束来确定 , 默认为 true 。
fetch: 表示抓取策略 , 默认为 FetchType.EAGER cascade: 表示默认的级联操作策略 , 可以指定为 ALL,PERSIST,MERGE,REFRESH 和 REMOVE 中的若干组合 , 默认为无级联操作 。
targetEntity: 表示该属性关联的实体类型 . 该属性通常不必指定 ,ORM 框架根据属性类型自动判断 targetEntity。
2.3.2 @JoinColumn(可选)
@JoinColumn 和 @Column 类似 , 介量描述的不是一个简单字段 , 而一一个关联字段 , 例如 . 描述一个 @ManyToOne 的字段 。
name: 该字段的名称 . 由于 @JoinColumn 描述的是一个关联字段 , 如 ManyToOne, 则默认的名称由其关联的实体决定。例如 , 实体 Order 有一个 user 属性来关联实体 User, 则 Order 的 user 属性为一个外键 , 其默认的名称为实体 User 的名称 + 下划线 + 实体 User 的主键名称
2.3.3 @OneToMany
@OneToMany(fetch=FetchType,cascade=CascadeType)
@OneToMany 描述一个一对多的关联 , 该属性应该为集体类型 , 在数据库中并没有实际字段 。
fetch: 表示抓取策略 , 默认为 FetchType.LAZY, 因为关联的多个对象通常不必从数据库预先读取到内存。
cascade: 表示级联操作策略 , 对于 OneToMany 类型的关联非常重要 , 通常该实体更新或删除时 , 其关联的实体也应当被更新或删除 例如 : 实体 User 和 Order 是 OneToMany 的关系 , 则实体 User 被删除时 , 其关联的实体 Order 也应该被全部删除
2.3.4 @OneToOne(可选)
@OneToOne(fetch=FetchType,cascade=CascadeType)
@OneToOne 描述一个一对一的关联 。
fetch: 表示抓取策略 , 默认为 FetchType.LAZY 。
cascade: 表示级联操作策略。
2.3.5 @ ManyToMany(可选)
@ManyToMany 描述一个多对多的关联 . 多对多关联上是两个一对多关联 , 但是在 ManyToMany 描述中 , 中间表是由 ORM 框架自动处理。
targetEntity: 表示多对多关联的另一个实体类的全名 , 例如 :package.Book.class。
mappedBy: 表示多对多关联的另一个实体类的对应集合属性名称。
三、JPA入门案例和CRUD操作(单表)
3.1 创建客户实体类
package com.Kevin.domain; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; /** * 客户的实体类 * @author Kevin * 使用的注解都是JPA规范,所以导包要导入javax.persistence包下 * */ @Entity //表名该类是一个实体类 @Table(name="cst_customer") //建立当前类和数据库的对应关系 public class Customer{ @Id // @Column(name="cust_id") //表明对应数据库的主键字段是cust_id @GeneratedValue(strategy=GenerationType.IDENTITY) //指定主键生成策略,strategy:使用JPA中提供的主键生成策略,此属性是用不了;generator属性可以使用hibernate主键生成策略 private Long cust_id; @Column(name="cust_name") private String cust_name; @Column(name="cust_address") private String cust_address; @Column(name="cust_industry") private String cust_industry; @Column(name="cust_level") private String cust_level; @Column(name="cust_phone") private String cust_phone; @Column(name="cust_mobile") private String cust_mobile; public Long getCust_id() { return cust_id; } public void setCust_id(Long cust_id) { this.cust_id = cust_id; } public String getCust_name() { return cust_name; } public void setCust_name(String cust_name) { this.cust_name = cust_name; } public String getCust_address() { return cust_address; } public void setCust_address(String cust_address) { this.cust_address = cust_address; } public String getCust_industry() { return cust_industry; } public void setCust_industry(String cust_industry) { this.cust_industry = cust_industry; } public String getCust_level() { return cust_level; } public void setCust_level(String cust_level) { this.cust_level = cust_level; } public String getCust_phone() { return cust_phone; } public void setCust_phone(String cust_phone) { this.cust_phone = cust_phone; } public String getCust_mobile() { return cust_mobile; } public void setCust_mobile(String cust_mobile) { this.cust_mobile = cust_mobile; } @Override public String toString() { return "Customer [cust_id=" + cust_id + ", cust_name=" + cust_name + ", cust_address=" + cust_address + ", cust_industry=" + cust_industry + ", cust_level=" + cust_level + ", cust_phone=" + cust_phone + ", cust_mobile=" + cust_mobile + "]"; } }
3.2 编写CRUD测试代码
3.2.1 保存操作
//保存操作 @Test public void test1(){ //创建客户对象 Customer c=new Customer(); c.setCust_name("Kevin"); //1.获取EntityManager EntityManager em=JPAUtil.createEntityManager(); //2.获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //3.执行保存操作 em.persist(c); //4.提交事务 tx.commit(); //5.关闭资源 em.close(); }
3.2.2 更新操作(两种方式)
第一种:正常更新方式(update)
@Test public void test3(){ //1.获取EntityManager EntityManager em=JPAUtil.createEntityManager(); //2.获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //3.执行更新操作(将需要更新的对象查询出来) Customer c=em.find(Customer.class, 1l); //更新客户地址 c.setCust_address("China");
//4.提交事务 tx.commit(); //5.关闭资源 em.close(); }
第二种:合并方式保存(merge,将两个实体合并)
@Test public void test4(){ //1.获取EntityManager EntityManager em=JPAUtil.createEntityManager(); //2.获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //3.执行更新操作(将需要更新的对象查询出来) Customer c=em.find(Customer.class, 1l); //更新客户地址 c.setCust_mobile("66666666");; em.merge(c); //4.提交事务 tx.commit(); //5.关闭资源 em.close(); }
3.2.3 删除操作
//删除 @Test public void test5(){ //获取EntityManager对象 EntityManager em=JPAUtil.createEntityManager(); //获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //获取操作对象 Customer c=em.find(Customer.class, 1l); //删除对象 em.remove(c); //提交事务 tx.commit(); //关闭资源 em.close(); }
3.2.4 基本查询
//查询所有 /** * 涉及的对象: * JPA的Query: * 如何获取对象:EntityManager的createQuery(String sql) * 参数含义:JPAL:Java Persistence Query Language * 写法与HQL很相似,也是把表名换成类名,把字段名换成属性名称 * 在写查询所有时,不能直接用 from 类 * 需要使用select关键字 */ @Test public void test6(){ //获取EntityManager对象 EntityManager em=JPAUtil.createEntityManager(); //获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //获取JPA的查询对象Query Query query=em.createQuery("select c from Customer c"); //c是一个别名 //执行方法获取结果集 List list=query.getResultList(); for(Object o:list) System.out.println(o); //提交事务 tx.commit(); //关闭资源 em.close(); }
3.2.5 条件查询
@Test public void test7(){ //获取EntityManager对象 EntityManager em=JPAUtil.createEntityManager(); //获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //获取JPA的查询对象Query Query query=em.createQuery("select c from Customer c where cust_name like ?"); //给占位符赋值 query.setParameter(1, "%k%"); //执行方法获取结果集 List list=query.getResultList(); for(Object o:list) System.out.println(o); //提交事务 tx.commit(); //关闭资源 em.close(); }
//多条件查询 @Test public void test8(){ //获取EntityManager对象 EntityManager em=JPAUtil.createEntityManager(); //获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //获取JPA的查询对象Query Query query=em.createQuery("select c from Customer c where cust_name like ? and cust_level = ? "); //给占位符赋值 query.setParameter(1, "%K%"); query.setParameter(2, "5"); //执行方法获取结果集 List list=query.getResultList(); for(Object o:list) System.out.println(o); //提交事务 tx.commit(); //关闭资源 em.close(); }
TIps: 1. persist()方法相当于是save()操作;
2. remove()对应的是delete();
3. find()方法对应get()方法;
4. getReference()是延迟加载;
5. find()是立即加载;
6. uniqueResult()对应getSingleResult(),返回唯一的结果。
7. merge()和update()相似,但是merge干的活update有些不能干;
3.2.6 区别merge和update
当查询了一个对象后,关闭session,再次查询了该对象,并且修改了该对象。此时如果使用update方法时会报错,因为第一次查完后关闭了session,对象的状态转变成了托管态,而此时查询该对象,修改的时候是持久态,对象的状态是不一样的,在一级缓存外边还有一个修改对象。此时更新的话,由于两个对象的OID是一样的,但是却发生了修改,使用update的话,两个对象是不能合并的,只能用merge()方法将其更新,即将两个对象合并。
@Test public void test10(){ /** * 查询ID为1的客户 * 关闭EntityManager(清空了一级缓存和快照) * 修改id为1的客户的地址为America * 在此获取EntityManager * 再次查询ID为1的客户 * 更新刚才修改的客户 */ //获取EntityManager对象 EntityManager em=JPAUtil.createEntityManager(); //获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); Customer c=em.find(Customer.class, 1L);//持久态 tx.commit(); em.close(); //修改客户信息 c.setCust_address("America");//托管态 //获取EntityManager对象 EntityManager em1=JPAUtil.createEntityManager(); //获取事务对象,并开启事务 EntityTransaction tx1=em.getTransaction(); tx1.begin(); //再次查询 Customer c1=em.find(Customer.class, 1L);//持久态 //更新操作 em1.persist(c);//将托管态转换为持久态,update(persist)方法是不行的,必须要用到merge方法才可以的 em1.merge(c); tx1.commit(); em1.close(); }
四、 JPA中实体一对多映射配置及操作
4.1 一对多实体类注解编写
客户实体类:
package com.Kevin.domain; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; /** * 客户的实体类 * @author Kevin * 使用的注解都是JPA规范,所以导包要导入javax.persistence包下 * */ @Entity //表情该类是一个实体类 @Table(name="cst_customer") //建立当前类和数据库的对应关系 public class Customer{ @Id // @Column(name="cust_id") //表明对应数据库的主键字段是cust_id @GeneratedValue(strategy=GenerationType.IDENTITY) //指定主键生成策略,strategy:使用JPA中提供的主键生成策略,此属性是用不了;generator属性可以使用hibernate主键生成策略 private Long cust_id; @Column(name="cust_name") private String cust_name; @Column(name="cust_address") private String cust_address; @Column(name="cust_industry") private String cust_industry; @Column(name="cust_level") private String cust_level; @Column(name="cust_phone") private String cust_phone; @Column(name="cust_mobile") private String cust_mobile; //一对多关系映射:一个客户可以有多个联系人 //targetEntity=Linkman.class 可以不写 @OneToMany(targetEntity=Linkman.class,mappedBy="customer",cascade=CascadeType.ALL,fetch=FetchType.EAGER) private Set<Linkman> linkmans=new HashSet<Linkman>(0); public Long getCust_id() { return cust_id; } public void setCust_id(Long cust_id) { this.cust_id = cust_id; } public String getCust_name() { return cust_name; } public void setCust_name(String cust_name) { this.cust_name = cust_name; } public String getCust_address() { return cust_address; } public void setCust_address(String cust_address) { this.cust_address = cust_address; } public String getCust_industry() { return cust_industry; } public void setCust_industry(String cust_industry) { this.cust_industry = cust_industry; } public String getCust_level() { return cust_level; } public void setCust_level(String cust_level) { this.cust_level = cust_level; } public String getCust_phone() { return cust_phone; } public void setCust_phone(String cust_phone) { this.cust_phone = cust_phone; } public String getCust_mobile() { return cust_mobile; } public void setCust_mobile(String cust_mobile) { this.cust_mobile = cust_mobile; } public Set<Linkman> getLinkmans() { return linkmans; } public void setLinkmans(Set<Linkman> linkmans) { this.linkmans = linkmans; } @Override public String toString() { return "Customer [cust_id=" + cust_id + ", cust_name=" + cust_name + ", cust_address=" + cust_address + ", cust_industry=" + cust_industry + ", cust_level=" + cust_level + ", cust_phone=" + cust_phone + ", cust_mobile=" + cust_mobile + "]"; } }
联系人实体类:
package com.Kevin.domain; /** * 创建联系人实体类 * `lkm_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT \'联系人编号(主键)\', `lkm_name` varchar(16) DEFAULT NULL COMMENT \'联系人姓名\', `lkm_cust_id` bigint(32) NOT NULL COMMENT \'客户id\', `lkm_gender` char(1) DEFAULT NULL COMMENT \'联系人性别\', `lkm_phone` varchar(16) DEFAULT NULL COMMENT \'联系人办公电话\', `lkm_mobile` varchar(16) DEFAULT NULL COMMENT \'联系人手机\', `lkm_email` varchar(64) DEFAULT NULL COMMENT \'联系人邮箱\', `lkm_qq` varchar(16) DEFAULT NULL COMMENT \'联系人qq\', `lkm_position` varchar(16) DEFAULT NULL COMMENT \'联系人职位\', `lkm_memo` varchar(512) DEFAULT NULL COMMENT \'联系人备注\', */ import java.io.Serializable; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity @Table(name="cst_linkman") public class Linkman implements Serializable { @Id @Column(name="lkm_id") @GeneratedValue(strategy=GenerationType.IDENTITY) private Long lkm_id; @Column(name="lkm_name") private String lkm_name; @Column(name="lkm_gender") private String lkm_gender; @Column(name="lkm_mobile") private String lkm_mobile; @Column(name="lkm_phone") private String lkm_phone; @Column(name="lkm_email") private String lkm_email; @Column(name="lkm_qq") private String lkm_qq; @Column(name="lkm_position") private String lkm_position; @Column(name="lkm_memo") private String lkm_memo; //一对多关系影射 //从表实体包含主表实体的对象引用 @ManyToOne(targetEntity=Customer.class,cascade=CascadeType.ALL) @JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id") private Customer customer; public Long getLkm_id() { return lkm_id; } public void setLkm_id(Long lkm_id) { this.lkm_id = lkm_id; } public String getLkm_name() {以上是关于[ SSH框架 ] Hibernate框架学习之四(JPA)的主要内容,如果未能解决你的问题,请参考以下文章