Entity的映射
虽然在前面,我们给出了表格。但书推荐我们先设计代码,然后根据代码来设计数据库,用户需求-》代码设计-》数据库设计。但我觉得大项目可能不会这样。
类和数据的映射
//表明这是 @javax.persistence.Entity,缺省Entity的名字是类名,如果需要特指,可通过用name参数。
@Entity(name = "PublisherEntity")
/*可以不设置,缺省的表名字就是entity的名字,本例如果不设定,则为PublisherEntity,现表名为Publishers。如果我们允许创建表格(注意,这是危险的,在生产环境中应当禁止)。我们可以在此设置主键外的其他key,例如:
* @Table(name = "Books", //表名
* uniqueConstraints = { @UniqueConstraint(name = "Books_ISBNs", columnNames = { "isbn" })}, // unique key
* indexes = { @Index(name = "Books_Titles", columnList = "title")}) // key */
@Table(name = "Publishers",
indexes = {@Index(name = "Publishers_Names", columnList = "PublisherName"))
//可以不设置,缺省为AccessType.PROPERTY,表示表的列名字是根据getter和setter方法,即Java Bean property;另一个值是就AccessType.FIELD,即根据field的名字。一般应使用缺省值。
@Access(AccessType.FIELD)
public class Publisher implements Serializable{ .....}
主键映射:单一主键
@Entity(name = "PublisherEntity")
@Table(name = "Publishers", indexes = {@Index(name = "Publishers_Names", columnList = "PublisherName"))})
public class Publisher implements Serializable{
private long id; //这就是field
private String name;
private String address;
/* 对于标记annotation,我们要么都加载field上,要么都加载property上,不要两者混用,避免出现混淆
*【1】我们首先要设定surrogate key,也就是SQL中的primary key,可能是单列,也可能是联合主键。*/
// 1.1】设置主键。主键是唯一的,如果代码没有设置@Id,如果有getId和setId,则视为该方法有@Id。
@Id
//【2】设置自动生成的值
// 2.1】如果是MySQL主键的AUTO_INCREMENT(Microsoft SQL Server和Sysbase中称为IDENTITY),为@GeneratedValue(strategy = GenerationType.IDENTITY)
/* 2.2】通过生成器的方式来设置主键的生成。生成器是全局有效的,在一处设置后,其他也可以使用,生成器的定义可以在class上,但是@GeneratedValue必须在property或者field上。有GenerationType.SEQUENCE和GenerationType.TABLE两种,对应@SequenceGenerator和@TableGenerator。
➤ @SequenceGenerator,如何产生主键的值(在Oracle中称为sequence,也就是流水号的生产方式),属性有initialValue(初始值缺省0),allocationSize(步进值,缺省50)。
➤ @TableGenerator,使用一个专门的表格来存放当前的流水号,如例子所示:使用了表SurrogateKeys,主键为TableName,值为Publishers,另一列为KeyValue,初始值11923,步进为1;也就是说在当前表增加一个entry时,表SurrogateKeys中TableName=Publishers的KeyValue加一,并将这个值作为本entity的id(列为PublisherId)的值。需要注意的是table,pkColumnName,pkColumnValue,valueColumnName的缺省值并没有在JPA中规定,因此不同的实现会有不同,为了避免歧义,建议明确定义。然而,我们很少使用这种方式,可能用于历史遗留项目。GenerationType.IDENTITY或GenerationType.SEQUENCE能满足我们的要求*/
@GeneratedValue(strategy = GenerationType.TABLE, generator = "PublisherGenerator")
@TableGenerator(name = "PublisherGenerator", table = "SurrogateKeys",
pkColumnName = "TableName", pkColumnValue = "Publishers",
valueColumnName = "KeyValue", initialValue = 11923, allocationSize = 1)
//【3】@Column用于对列的设置
// @Column的参数 name:指定列名;insertable和updatable是权限
// @Column的schema相关参数
/* - nullable:NULL或者NOT NULL;
* - unique相当于@UniqueConstraint,缺省为false;
* - length用于VARBINARY和VARCHAR的长度,缺省为255;
* - scale和precision用于Decimal;
* - columnDefinition指定生成列的SQL,但是由于不同SQL Server在语句上会有区别,一旦使用,就很难迁移到其他数据库,因此不建议 */
@Column(name = "PublisherId")
public final long getId() {
return id;
}
}
主键映射:复合主键
方式1:使用@IdClass
表格SomeJoinTable有联合组件(fooParentTableSk,barParentTableSk),代码如下:
public class JoinTableCompositeId implements Serializable{
private long fooParentTableSk;
private long barParentTableSk;
public long getFooParentTableSk() { ... }
public void setFooParentTableSk(long fooParentTableSk) { ... }
public long getBarParentTableSk() { ... }
public void setBarParentTableSk(long barParentTableSk) { ... }
}
@Entity
@Table(name = "SomeJoinTable")
@IdClass(JoinTableCompositeId.class)
public class JoinTableEntity implements Serializable{
private long fooParentTableSk;
private long barParentTableSk;
...
@Id
public long getFooParentTableSk() { ... }
public void setFooParentTableSk(long fooParentTableSk) { ... }
@Id
public long getBarParentTableSk() { ... }
public void setBarParentTableSk(long barParentTableSk) { ... }
...
}
方式2:使用@EmbeddedId
我们看到方式1中,在getter和setter的代码有重复,可以采用下面的方式:
@Embeddable
public class JoinTableCompositeId implements Serializable{
private long fooParentTableSk;
private long barParentTableSk;
public long getFooParentTableSk() { ... }
public void setFooParentTableSk(long fooParentTableSk) { ... }
public long getBarParentTableSk() { ... }
public void setBarParentTableSk(long barParentTableSk) { ... }
}
@Entity
@Table(name = "SomeJoinTable")
public class JoinTableEntity implements Serializable{
private JoinTableCompositeId id;
@EmbeddedId
public JoinTableCompositeId getId() { ... }
public void setId(JoinTableCompositeId id) { ... }
}
数据类型映射
JPA中property的数据类型 | 数据库中column的数据类型 |
---|---|
short,Short | SMALLINT, INTEGER, BIGINT或相应的类型 |
int,Integer | INTEGER, BIGINT或相应的类型 |
long,Long | BIGINT或相应的类型 |
float, Float, double, Double, BigDecimal | DECIMAL 或相应的类型 |
byte,Byte | BINARY, SMALLINT, INTEGER, BIGINT或相应的类型 |
char,Char | CHAR, VARCHAR, BINARY, SMALLINT, INTEGER, BIGINT或相应的类型 |
boolean,Boolean | BOOLEAN, BIT, SMALLINT, INTEGER, BIGINT, CHAR, VARCHAR或相应的类型 |
byte[],Byte[] | BINARY, VARBINARY或相应的类型 |
char[], Character[],String | CHAR, VARCHAR, BINARY,VARBINARY或相应的类型 |
java.util.Date, Calendar | DATE, DATETIME, TIME或相应的类型,需要加上@Temporal |
java.sql.Timestamp | DATETIME |
java.sql.Date | DATE |
java.sql.Time | Time |
Enum | SMALLINT, INTEGER, BIGINT, CHAR, VARCHAR或相应的类型,可以通过@Enumerated来变更存储方式,在后面介绍。 |
Serializable | VARBINARY或相应的类型,用于Java的序列化和反序列化 |
/*使用@Basic可以进行上表中的类型匹配,optional表示是否可以为null,本例表示NOT NULL。对于原型,如int,double等是没有null这一说的,因此在原型时,不设置option说明,数据库中必定是NOT NULL */
@Basic(optional=false)
public final String getIsbn() {
return isbn;
}
设置Persistence
Persistent Unit Scope是配置和Entity类,其中的EntityManager实例只能方位该持续化单元,不能越界。Entity类可以属于多个持续化单元。持续化单元通过persistence.xml配置,必须放置在META-INF/下。一个web项目有多个META-INF:mappings.war!/META-INF 这不在classes/目录下,不能被class文件访问,用来放置Servlet容器所需的文件,所以不能放这里 mappings.war!/WEB-INF/classes/META-INF 放在这里,在eclipse中,即在resources/下串接META-INF/ mappings.war!/WEB-INF/lib/something.jar!/META-INF 这是其他jar包所带的META-INF目录,我们也放不进去,而且其作用只在该jar包内
下面是例子的persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<!-- 有一个或者多个persistence-unit -->
<!-- transaction-type:在J2EE应用服务器中缺省为JTA(Java Transaction API),在J2SE和简单的servlet容器中缺省为标准本地事务 RESOURCE_LOCAL。为避免歧义,我们应明确设置 -->
<!-- persistence-unit里面可以为空,但是如果设置,必须要顺序 -->
<persistence-unit name="EntityMappingsTest" transaction-type="RESOURCE_LOCAL">
<!-- 首先是<description>,小例子不提供 -->
<!-- provider:具体的javax.persistence.spi.PersistenceProvider实现,缺省值为classpath中第一个JPA的实现 -->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- 如果persistence-unit的transaction-type为JTA,使用<jta-data-source>,如果为RESOURCE_LOCAL,使用<non-jta-data-source>。他们的区别在于后者使用EntityTransaction接口,而前者使用UserTransaction接口,缺省是前者(JTA)。它们均采用JNDI(Java Naming and Directory Interface)方式,给出数据源。 -->
<non-jta-data-source>java:comp/env/jdbc/learnTest</non-jta-data-source>
<!-- <mapping-file>:基于classpath的XML mapping file,如果不指定,缺省为orm.xml,可以设置多个mapping-file -->
<!-- <jar-file>:JPA实现将对jar包进行绑定标记扫描,如果里面有@Entity,@Embeddable,@javax.persistence.MappedSuperclass或者@javax.persistence.Converter,加入本持续化单元中,可以设置多个jar-file -->
<!-- <class>:JPA实现将对这个class加入到持续化单元中,这个class必须带有@Entity,@Embeddable,@javax.persistence.MappedSuperclass或者@javax.persistence.Converter。可以设置多个class -->
<!-- 设置<exclude-unlisted-classes/>或者<exclude-unlisted-classes>true</exclude-unlisted-classes>表示只关注在jar-file和在class中所设置的,不扫描其他。删除<exclude-unlisted-classes/>或者<exclude-unlisted-classes>false</exclude-unlisted-classes>则表示将扫描classpath位置;如果本文件在JAR文件,则扫描JAR文件的classes,如果本文件位于classes中的某个特定目录,则只扫描该目录下的文件(例如指定到某个package)。 -->
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<!-- <shared-cache-mode>:是否缓存entity,-->
<!-- ➤ NONE表示不缓存,-->
<!-- ➤ ALL表示缓存所有的entities。-->
<!-- ➤ ENABLE_SELECTIVE 表示只缓存带有@Cacheable或者@Cacheable(true)标识的entity -->
<!-- ➤ DISABLE_SELECTIVE 表示除了@Cacheable(false)外均缓存 -->
<!-- ➤ UNSPECIFIED 表示有JPA的提供者来决定,Hibernate ORM缺省为ENABLE_SELECTIVE,但采用这种方式,对于移植可能会存在混淆,应明确设定 -->
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<!-- <validation-mode> -->
<!-- ➤ NONE表示不使用Bean validator,-->
<!-- ➤ CALLBACK表示在写操作(insert,update,delete)前进行validate -->
<!-- ➤ AUTO,如果classpath中存在Bean validator provider,则CALLBACK,不存在则NONE -->
<!-- 如果使用validate,而我们自定义了spring framework的validator,JPA将忽略这个自定义。因此建议使用NONE,在数据持久化之前进行校验,而不是在持久化这个层面。 -->
<validation-mode>NONE</validation-mode>
<!-- <properties>以name-value的方式提供其他JPA属性(如JDBC连接,用户,密码,schema产生成设置等)以及提供者特有的属性(如Hibernate设置)。 -->
<properties>
<!-- 禁止schema生成,即不根据entity创建表格 -->
<property name="javax.persistence.schema-generation.database.action" value="none" />
</properties>
</persistence-unit>
</persistence>
持续化的代码
我们以servlet为例,演示如何查询表格和insert表格。@WebServlet(
name ="EntityServlet",
urlPatterns = "/entities",
loadOnStartup = 1)
public class EntityServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private final Random random;
//Entity管理器:在初始化时获取,在结束是关闭
private EntityManagerFactory factory;
public EntityServlet() {
super();
try {
this.random = SecureRandom.getInstanceStrong();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
/** 【1.1】在初始化时创建EntityManagerFactory。持续化单元的名字见persistence.xml。
* 对于完全J2EE server(Tomcat不是),不需要这样,采用:
* @PersistenceContext("EntityMappingsTest")
* EntityManagerFactory factory; */
@Override
public void init() throws ServletException {
super.init();
this.factory = Persistence.createEntityManagerFactory("EntityMappingsTest");
}
/**【1.2】在结束时关闭EntityManagerFactory */
@Override
public void destroy() {
this.factory.close();
super.destroy();
}
/** 【2】我们在doGet中演示获取表格内容,其中,演示了对transaction处理的常规代码 */
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
EntityManager manager = null;
EntityTransaction transaction = null;
try{
manager = this.factory.createEntityManager();
transaction = manager.getTransaction();
transaction.begin();
//读表Publisher(简单的采用全获取的方式)
CriteriaBuilder builder = manager.getCriteriaBuilder();//返回用于CriteriaQuery objects的CriteriaBuilder实例
CriteriaQuery<Publisher> q1 = builder.createQuery(Publisher.class);
Root<Publisher> q1Root = q1.from(Publisher.class);
TypedQuery<Publisher> queryResult = manager.createQuery(q1.select(q1Root));
request.setAttribute("publishers", queryResult.getResultList());
//读表Author(简单的采用全获取的方式)
CriteriaQuery<Author> q2 = builder.createQuery(Author.class);
request.setAttribute("authors", manager.createQuery(q2.select(q2.from(Author.class))).getResultList());
CriteriaQuery<Book> q3 = builder.createQuery(Book.class);
request.setAttribute("books", manager.createQuery(q3.select(q3.from(Book.class))).getResultList());
transaction.commit();
request.getRequestDispatcher("/WEB-INF/jsp/view/entities.jsp").forward(request, response);
}catch(Exception e){
if(transaction != null && transaction.isActive())
transaction.rollback();
e.printStackTrace(response.getWriter()); //简单地在页面输出,正式项目不会如此处理
}finally{
if(manager != null && manager.isOpen())
manager.close();
}
}
/** 【3】我们在doPost中演示insert */
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
EntityManager manager = null;
EntityTransaction transaction = null;
try{
manager = this.factory.createEntityManager();
transaction = manager.getTransaction();
transaction.begin();
Publisher publisher = new Publisher();
publisher.setName("John Wiley & Sons");
publisher.setAddress("1234 Baker Street");
manager.persist(publisher);
Author author = new Author();
author.setName("Nicholas S. Williams");
author.setEmailAddress("nick@example.com");
manager.persist(author);
Book book = new Book();
book.setIsbn("" + this.random.nextInt(Integer.MAX_VALUE));
book.setTitle("Professional Java for Web Applications");
book.setAuthor("Nicholas S. Williams");
book.setPublisher("John Wiley & Sons");
book.setPrice(59.99D);
manager.persist(book);
transaction.commit();
response.sendRedirect(request.getContextPath() + "/entities");
}catch(Exception e){
if(transaction != null && transaction.isActive())
transaction.rollback();
e.printStackTrace(response.getWriter());
}finally{
if(manager != null && manager.isOpen())
manager.close();
}
}
}
相关链接: 我的Professional Java for Web Applications相关文章