实体对象以及注解介绍
具有ORM元数据的领域对象称为实体(Entity),按JPA的规范,实体具备以下的条件:
必须使用javax.persistence.Entity注解或者在XML映射文件中有对应的元素;
必须具有一个不带参的构造函数,类不能声明为final,方法和需要持久化的属性也不能声明为final;
如果游离状的实体对象需要以值的方式进行传递,如通Session bean的远程业务接口传递,则必须实现Serializable接口;
需要持久化的属性,其访问修饰符不能是public,它们必须通过实体类方法进行访问。
使用注解元数据
基本注解:
例子:
@Entity(name = "T_TOPIC") ①
public class Topic implements Serializable ...{
@Id ②-1
@GeneratedValue(strategy = GenerationType.TABLE) ②-2
@Column(name = "TOPIC_ID") ②-3
private int topicId;
@Column(name = "TOPIC_TITLE", length = 100) ③
private String topicTitle;
@Column(name = "TOPIC_TIME") @Temporal(TemporalType.DATE) ④
private Date topicTime;
@Column(name = "TOPIC_VIEWS")
private int topicViews;
...
}
解释:
① Entity标明该类(Topic)为一个实体类,它对应数据库中的表表名是T_TOPIC,这里也可以写成: @Entity
@Table(name = "T_TOPIC") 其作用都是一样的
②-1 Id标明该属性对应数据表中的主键
②-2 GeneratedValue通过strategy属性指明主键生成策略,默认情况下,JPA自动选择一个最适合底层数据库的主键生成策略。在javax.persistence.GenerationType中定义了以下几种可供选择的策略:
1) IDENTITY:表自增键字段,Oracle不支持这种方式;
2) AUTO: JPA自动选择合适的策略,是默认选项;
3) SEQUENCE:通过序列产生主键,通过@SequenceGenerator注解指定序列名,MySql不支持这种方式;
4) TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。
②-3 Column标明这个属性是数据表中的一列,该列的名字是TOPIC_ID
③ Column的一个属性length指明的是该属性的允许的长度。(个人认为设定该属性只是对于程序中操作该属性时增加了一验证过程,对数据库中该列原来的设置并没有影响,但是length属性指定的值必须不能大于数据库创建表时给该列限制的最大长度否则会出错)
④ Temporal(TemporalType.DATE):如果属性是时间类型,因为数据表对时间类型有更严格的划分,所以必须指定具体时间类型。在javax.persistence.TemporalType枚举中定义了3种时间类型:
1) DATE :等于java.sql.Date
2) TIME :等于java.sql.Time
3) TIMESTAMP :等于java.sql.Timestamp
继承关系注解:
对继承关系进行注解,必须在父类中声明继承实体的映射策略。
例子:
@Entity(name = "T_TOPIC")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) ① @DiscriminatorColumn(name = "TOPIC_TYPE", discriminatorType =
DiscriminatorType.INTEGER, length = 1) ②
@DiscriminatorValue(value="1")③
public class Topic implements Serializable ...{ … }
解释:
① Inheritance通过strategy属性指明实体的继承策略。
在javax.persistence.InheritanceType定义了3种映射策略:
1) SINGLE_TABLE:父子类都保存到同一个表中,通过字段值进行区分。
2) JOINED:父子类相同的部分保存在同一个表中,不同的部分分开存放,通过表连接获取完整数据;
3) TABLE_PER_CLASS:每一个类对应自己的表,一般不推荐采用这种方式。
② DiscriminatorColumn如果继承策略采用第一种继承策略,则需要指明区分父子类的字段,DiscriminatorColumn就是用来指明区分字段的注解。
③DiscriminatorValue 同样的采用第一种继承策略通过字段区分父子类,则用这个注解给该实体的区分字段赋值在这里赋的值为”1”.
关联关系注解:
例子:
@Entity @DiscriminatorValue(value="2") ①
public class PollTopic extends Topic ...{②继承于Topic实体
private boolean multiple; ③
@Column(name = "MAX_CHOICES")
private int maxChoices; @OneToMany(mappedBy="pollTopic",cascade=CascadeType.ALL) ④
private Set options = new HashSet();
//省略get/setter方法
}
解释:
① 通过@DiscriminatorValue将区分字段TOPIC_TYPE的值为2。由于PollTopic实体继承于Topic实体,其它的元数据信息直接从Topic获得。
④ OneToMany指定了一个一对多的关联关系,mappedBy属性指定“Many”方类引用“One”方类的属性名;cascade属性指明了级联方式(如果这里不指定为CascadeType.ALL的话,那么有关联关系的两个对象在做保存和删除操作时要分别来进行)建议:尽可能使用cascade=CascadeType.ALL来减少持久化操作的复杂性和代码量
注意:JPA规范规定任何属性都默认映射到表中,所以虽然我们没有给③处的multiple属性提供注解信息,但JPA将按照 默认的规则对该字段进行映射:字段名和属性名相同,类型相同。如果我们不希望将某个属性持久化到数据表中,则可以通过@Transient注解显式指定:
@Transient
private boolean tempProp1;
@Entity(name="T_POLL_OPTION")
Public class PollOption implements Serializable ...{
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@Column(name = "OPTION_ID")
private int optionId;
@Column(name = "OPTION_ITEM")
private String optionItem;
@ManyToOne ①
@JoinColumn(name="TOPIC_ID", nullable=false) ②
private PollTopic pollTopic;
}
解释:
① ManyToOne描述了多对一的关联关系,他是对该类引用的”One”类(PollTopic)的属性(pollTopic)进行注解的。
② JoinColumn指定关联”One”(PollTopic)实体所对应表的“外键”。
Lob字段的注解:
在JPA中Lob类型类型的持久化很简单,仅需要通过特殊的Lob注解就可以达到目的。
例子:
@Lob ①-1
@Basic(fetch = FetchType.EAGER) ①-2
@Column(name = "POST_TEXT", columnDefinition = "LONGTEXT NOT NULL") ①-3
private String postText;
@Lob
@Basic(fetch = FetchType. LAZY) ②-2
@Column(name = "POST_ATTACH", columnDefinition = "BLOB") ②-3
private byte[] postAttach;
解释:
①-1 JPA 通过@Lob将属性标注为Lob类型 ;
①-2 通过@Basic指定Lob类型数据的获取策略,FetchType.EAGER表示非延迟 加载,而FetchType. LAZY表示延迟加载 ;
①-3 通过@Column的columnDefinition属性指定数据表对应的Lob字段类型。
使用XML元数据
除了使用注解提供元数据信息外,JPA也允许我们通过XML提供元数据信息。按照JPA的规范,如果你提供了XML元数据描述信息,它将覆盖实体类中的注解元数据信息。XML元数据信息以 orm.xml命名,放置在类路径的META-INF目录下。
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
version="1.0">
①实体对象所在的包
<package>com.baobaotao.domain</package>
<entity class="Topic">
②Topic实体配置
<table name="T_TOPIC" />
<attributes>
<id name="topicId">
<column name="TOPIC_ID"/>
<generated-value strategy="TABLE" />
</id>
<basic name="topicTitle">
<column name="TOPIC_TITLE" length="30" />
</basic>
<basic name="topicTime">
<column name="TOPIC_TIME" />
<temporal>DATE</temporal>
</basic>
<basic name="topicViews">
<column name="TOPIC_VIEWS" />
</basic>
</attributes>
</entity>
<entity class="PollTopic">
②PollTopic实体配置
<discriminator-value>2</discriminator-value>
<attributes>
<basic name="maxChoices">
<column name="MAX_CHOICES" />
</basic>
<one-to-many name="options" mapped-by="pollTopic">
<cascade>
<cascade-all/>
</cascade>
</one-to-many>
</attributes>
</entity>
<entity class="PollOption">
②PollOption实体配置
<table name="T_POLL_OPTION" />
<attributes>
<id name="optionId">
<column name="OPTION_ID" />
<generated-value strategy="TABLE" />
</id>
<basic name="optionItem">
<column name="OPTION_ITEM"/>
</basic>
<many-to-one name="pollTopic" >
<join-column name="TOPIC_ID" nullable="false"/>
</many-to-one>
</attributes>
</entity>
<entity class="Post">
②Post实体配置
<table name="T_POST" />
<attributes>
<id name="postId">
<column name="POST_ID" />
<generated-value strategy="TABLE" />
</id>
<basic name="postText" fetch="EAGER">
<column name="POST_TEXT" column-definition="LONGTEXT NOT NULL"/>
<lob/>
</basic>
<basic name="postAttach" fetch="LAZY">
<column name="POST_ATTACH" column-definition="BLOB"/>
<lob/>
</basic>
</attributes>
</entity>
</entity-mappings>
使用这个orm.xml来描述实体信息的话,这里并没有标明两个继承类之间的关系,其继承信息将从实体类反射信息获取。
到这里我们的实体描述结束了,当然我们只是做了比较简单的描述,对于那些复杂的信息描述并没有进行讲述。实体描述结束了,有人会问如果我要来操作这些实体该怎么操作?这就是我们接下来要讲述的问题。
EntityManager介绍
实体对象由实体管理器进行管理,JPA使用javax.persistence.EntityManager代表实体管理器。实体管理器和持久化上下文关联,持久化上下文是一系列实体的管理环境,我们通过EntityManager和持久化上下文进行交互。
有两种类型的实体管理器:
容器型:容器型的实体管理器由容器负责实体管理器之间的协作,在一个JTA事务中,一个实体管理器的持久化上下文的状态会自动广播到所有使用EntityManager的应用程序组件中。Java EE应用服务器提供的就是管理型的实体管理器;
应用程序型:实体管理器的生命周期由应用程序控制,应用程序通过javax.persistence.EntityManagerFactory的createEntityManager创建EntityManager实例。
EntityManager的创建过程图:
我们在程序中的创建EntityManager的代码:
EntityManagerFactory currentManagerFactory = Persistence.createEntityManagerFactory(persistenceUtilName);
EntityManager em = currentManagerFactory.createEntityManager();
实体的状态
实体对象拥有以下4个状态,这些状态通过调用EntityManager接口方法发生迁移:
1) 新建态:新创建的实体对象,尚未拥有持久化主键,没有和一个持久化上下文关联起来。
2) 受控态:已经拥有持久化主键并和持久化上下文建立了联系;
3) 游离态:拥有持久化主键,但尚未和持久化上下文建立联系;
4) 删除态:拥有持久化主键,已经和持久化上下文建立联系,但已经被安排从数据库中删除。
通过EntityManager中的接口方法可以改变实体对象的状态:
a) void persist(Object entity)
通过调用EntityManager的persist()方法,新实体实例将转换为受控状态。这意谓着当persist ()方法所在的事务提交时,实体的数据将保存到数据库中。如果实体已经被持久化,那么调用persist()操作不会发生任何事情。如果对一个已经删除的 实体调用persist()操作,删除态的实体又转变为受控态。如果对游离状的实体执行persist()操作,将抛出 IllegalArgumentException。
在一个实体上调用persist()操作,将广播到和实体关联的实体上,执行相应的级联持久化操作;
b) void remove(Object entity)
通过调用remove()方法删除一个受控的实体。如果实体声明为级联删除(cascade=REMOVE 或者cascade=ALL ),被关联的实体也会被删除。在一个新建状态的实体上调用remove()操作,将被忽略。如果在游离实体上调用remove()操作,将抛出 IllegalArgumentException,相关的事务将回滚。如果在已经删除的实体上执行remove()操作,也会被忽略;
c) void flush()
将受控态的实体数据同步到数据库中;
d) T merge(T entity)
将一个游离态的实体持久化到数据库中,并转换为受控态的实体;
e) T find(Class entityClass, Object primaryKey)
以主键查询实体对象,entityClass是实体的类,primaryKey是主键值;
f) Query
JPA使用javax.persistence.Query接口代表一个查询实例,Query实例由EntityManager通过指定查询语句构建。该接口拥有众多执行数据查询的接口方法:
◆Object getSingleResult():执行SELECT查询语句,并返回一个结果;
◆List getResultList() :执行SELECT查询语句,并返回多个结果;
◆Query setParameter(int position, Object value):通过参数位置号绑定查询语句中的参数,如果查询语句使用了命令参数,则可以使用Query setParameter(String name, Object value)方法绑定命名参数;
◆Query setMaxResults(int maxResult):设置返回的最大结果数;
◆int executeUpdate():如果查询语句是新增、删除或更改的语句,通过该方法执行更新操作;
还有就是关于JPA的查询语言,JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,其查询语言类似于HQL语句,在这里就不再赘述。