开始接触jpa,跟大家一起学习。有什么不足请指教!
1. 什么是ORM?
对象-关系映射(Object-Relational Mapping,简称ORM),面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射(ORM)系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。
2.为什么使用ORM?
当我们实现一个应用程序时(不使用O/R Mapping),我们可能会写特别多数据访问层的代码,从数据库保存、删除、读取对象信息,而这些代码都是重复的。而使用ORM则会大大减少重复性代码。对象关系映射(Object Relational Mapping,简称ORM),主要实现程序对象到关系数据库数据的映射。
3.对象-关系映射解释:
A . 简单:ORM以最基本的形式建模数据。比如ORM会将MySQL的一张表映射成一个Java类(模型),表的字段就是这个类的成员变量
B . 精确:ORM使所有的mysql数据表都按照统一的标准精确地映射成java类,使系统在代码层面保持准确统一
C .易懂:ORM使数据库结构文档化。比如MySQL数据库就被ORM转换为了java程序员可以读懂的java类,java程序员可以只把注意力放在他擅长的java层面(当然能够熟练掌握MySQL更好)
D.易用:ORM包含对持久类对象进行CRUD操作的API,例如create(), update(), save(), load(), find(), find_all(), where()等,也就是讲sql查询全部封装成了编程语言中的函数,通过函数的链式组合生成最终的SQL语句。通过这种封装避免了不规范、冗余、风格不统一的SQL语句,可以避免很多人为Bug,方便编码风格的统一和后期维护。
4. 对JPA的理解
JPA的总体思想和现有hibernate、TopLink,JDO等ORM框架大体一致。总的来说,JPA包括以下3方面的技术:
1. ORM映射元数据,JPA支持XML和JDK 5.0注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中;
2. JPA的API,用来操作实体对象,执行CRUD操作,框架在后台替我们完成所有的事情,开发者从繁琐的JDBC和SQL代码中解脱出来。
3. 查询语言,这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合。
————————————————–
6、实体生命周期
描述了实体对象从创建到受控、从删除到游离的状态变换。对实体的操作主要就是改变实体的状态。
1.New,新创建的实体对象,没有主键(identity)值
2.Managed,对象处于Persistence Context(持久化上下文)中,被EntityManager管理
3.Detached,对象已经游离到Persistence Context之外,进入Application Domain
4.Removed, 实体对象被删除
EntityManager提供一系列的方法管理实体对象的生命周期,包括:
1.persist, 将新创建的或已删除的实体转变为Managed状态,数据存入数据库。
2.remove,删除受控实体
3.merge,将游离实体转变为Managed状态,数据存入数据库。
如果使用了事务管理,则事务的commit/rollback也会改变实体的状态。
7. 实体关系映射(ORM)
- 基本映射
2. ID生成策略
ID对应数据库表的主键,是保证唯一性的重要属性。JPA提供了以下几种ID生成策略
(1) GeneratorType.AUTO ,由JPA自动生成
(2) GenerationType.IDENTITY,使用数据库的自增长字段,需要数据库的支持(如SQL Server、MySQL、DB2、Derby等)
(3) GenerationType.SEQUENCE,使用数据库的序列号,需要数据库的支持(如Oracle)
(4) GenerationType.TABLE,使用指定的数据库表记录ID的增长 需要定义一个TableGenerator,在@GeneratedValue中引用。例如:
@TableGenerator( name=”myGenerator”, table=”GENERATORTABLE”, pkColumnName = “ENTITYNAME”, pkColumnValue=”MyEntity”, valueColumnName = “PKVALUE”, allocationSize=1 )
@GeneratedValue(strategy = GenerationType.TABLE,generator=”myGenerator”)
- 关联关系
JPA定义了one-to-one、one-to-many、many-to-one、many-to-many 4种关系。
对于数据库来说,通常在一个表中记录对另一个表的外键关联;对应到实体对象,持有关联数据的一方称为owning-side,另一方称为inverse-side。
为了编程的方便,我们经常会希望在inverse-side也能引用到owning-side的对象,此时就构建了双向关联关系。 在双向关联中,需要在inverse-side定义mappedBy属性,以指明在owning-side是哪一个属性持有的关联数据。
对关联关系映射的要点如下:
其中 many-to-many关系的owning-side可以使用@JoinTable声明自定义关联表,比如Book和Author之间的关联表:
@JoinTable(name = “BOOKAUTHOR”, joinColumns = { @JoinColumn(name = “BOOKID”, referencedColumnName = “id”) }, inverseJoinColumns = { @JoinColumn(name = “AUTHORID”, referencedColumnName = “id”) })
关联关系还可以定制延迟加载和级联操作的行为(owning-side和inverse-side可以分别设置):
通过设置fetch=FetchType.LAZY 或 fetch=FetchType.EAGER来决定关联对象是延迟加载或立即加载。
通过设置cascade={options}可以设置级联操作的行为,其中options可以是以下组合:
CascadeType.MERGE 级联更新
CascadeType.PERSIST 级联保存
CascadeType.REFRESH 级联刷新
CascadeType.REMOVE 级联删除
CascadeType.ALL 级联上述4种操作 - 继承关系
JPA通过在父类增加@Inheritance(strategy=InheritanceType.xxx)来声明继承关系。A支持3种继承策略:
(1) 单表继承(InheritanceType.SINGLETABLE),所有继承树上的类共用一张表,在父类指定(@DiscriminatorColumn)声明并在每个类指定@DiscriminatorValue来区分类型。
(2) 类表继承(InheritanceType.JOINED),父子类共同的部分公用一张表,其余部分保存到各自的表,通过join进行关联。
(3) 具体表继承(InheritanceType.TABLEPERCLASS),每个具体类映射到自己的表。
其中1和2能够支持多态,但是1需要允许字段为NULL,2需要多个JOIN关系;3最适合关系数据库,对多态支持不好。具体应用时根据需要取舍。
8. ORM的优缺点:
优点:
提高开发效率,降低开发成本
使开发更加对象化
可移植
可以很方便地引入数据缓存之类的附加功能
缺点:
自动化进行关系数据库的映射需要消耗系统性能。其实这里的性能消耗还好啦,一般来说都可以忽略之。
在处理多表联查、where条件复杂之类的查询时,ORM的语法会变得复杂。
到这里jpa已介绍完,接下来进行jpa开发案例讲解:
开发jpa应用有两种做法(hibernate也是):两种方案:
(一):先建表,后跟据表创建来编写配置文件和实体bean,使用这种开发方案人员是受到传统的数据库建模影响。
(二):先写配置文件和实体bean,然后再生成表,使用这种开发方案人员是采用领域建模思想(新建模思想),这种思想较前一种更加oop。(使用它要对oop思想很熟悉灵活运用)。
一.添加JPA配置文件 persistence.xml。
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="persistenceUnit"
transaction-type="RESOURCE_LOCAL">
<properties>
<property name="hibernate.jdbc.batch_size" value="30" />
<property name="hibernate.use_sql_comments" value="true" />
<property name="hibernate.dialect" value="org.hibernate......" />//hibernate方言
<!--自动输出schema创建DDL语句 -->
<property name="hibernate.hbm2ddl.auto" value="update" />
//表示数据存在,不改变时就不用更改,如果有变化,就把数据映射到数据库
<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" />
<property name="hibernate.connection.charSet" value="UTF-8" />
</properties>
</persistence-unit>
</persistence>
其中transaction-type="RESOURCE_LOCAL 是表示事务类型,RESOURCE_LOCAL,表示本地事务
事务两种类型:分全局事务和本地事务
全局事务常应用于应用服务器中(例如:同时操作多条数据库语句)但实现要使用全局事务jpa,一般由容器提供 jpa.getUserTrantion().begin()。这里面如何执行事务提交与回滚,使用的两次提交协议。(了解两次提交协议)。其他大部分用于本地事务(一个数据库操作)。
二. 添加JPA缓存配置文件ehcache.xml
<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>
public class User implements Serializable {
/**
* @author zjn
*/
private static final long serialVersionUID = 1L;
private Integer id; // id 这些属性字段实际表示的是表字段
private String name; // name
private String pwd; // pwd
private Integer age; // age
private Date creatTime; // creatTime
@Id @GeneratedValue //主键映射,并且制定增长方式
public Integer getId() { //get方法才表示定义一个属性。
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Lob @Basic(fetch=FetchType.LAZY)//指定该属性为大文本类型,它还可以用于字节 @Basic设为延迟加载可以防止
内存占用,当不用到该字段属性时,不会加载他,也就缓存中也就不会产生内存资源,这个注解一般用于大数据,一般大于1兆,
初始化时不会加载他。
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Transient//不希望属性与数据库字段做映射,不作为持久化字段,对应数据不会产生此字段
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Temporal(TemporalType.DATE)//用于时间属性映射
public Date getCreatTime() {
return creatTime;
}
public void setCreatTime(Date creatTime) {
this.creatTime = creatTime;
}
}
2.test测试类
public class JPATest {
private EntityManagerFactory entityManagerFactory;
private EntityManager entityManager;
private EntityTransaction transaction;
@Test //这个案例仅是测试用与下面代码无关,仅是测试讲解作用,下面所有代码是一个完整的测试
public void save(){
//跟获取seeionFactory对象一样,性质一样
entityManagerFactory = Persistence.createEntityManagerFactory("持久化单元的名称也就是persistenceUnit--》persistence.xml里");//执行这句
话的时候表就会根据实体创建数据库表下面不用执行就可以,下面是对表操作的
//跟获取session一样
entityManager = entityManagerFactory.createEntityManager();
//取得一个事务
transaction = entityManager.getTransaction();
transaction.begin();//开启事务
entityManager.persist(new User()) //对象由hibernate反射进行提供创建的 是由默认的构造函数,,所以实体内需要无参的构造函数
transaction.begin().commit();
transaction.close();
entityManagerFactory.close();
}
下面提供源码案例点击打开链接
后续会不断完善!