您的位置:首页 > 编程语言 > Java开发

Java for Web学习笔记(九九):持久化初探(4)JPA小例子(下)

2017-11-25 17:36 483 查看

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,ShortSMALLINT, INTEGER, BIGINT或相应的类型
int,IntegerINTEGER, BIGINT或相应的类型
long,LongBIGINT或相应的类型
float, Float, double, Double, BigDecimalDECIMAL 或相应的类型
byte,ByteBINARY, SMALLINT, INTEGER, BIGINT或相应的类型
char,CharCHAR, VARCHAR, BINARY, SMALLINT, INTEGER, BIGINT或相应的类型
boolean,BooleanBOOLEAN, BIT, SMALLINT, INTEGER, BIGINT, CHAR, VARCHAR或相应的类型
byte[],Byte[]BINARY, VARBINARY或相应的类型
char[], Character[],StringCHAR, VARCHAR, BINARY,VARBINARY或相应的类型
java.util.Date, CalendarDATE, DATETIME, TIME或相应的类型,需要加上@Temporal
java.sql.TimestampDATETIME
java.sql.DateDATE
java.sql.TimeTime
EnumSMALLINT, INTEGER, BIGINT, CHAR, VARCHAR或相应的类型,可以通过@Enumerated来变更存储方式,在后面介绍。
SerializableVARBINARY或相应的类型,用于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相关文章
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: