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,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相关文章
相关文章推荐
- Java for Web学习笔记(一百):持久化初探(5)Enum、时间和lob的类型匹配
- Java for Web学习笔记(一零七):Spring框架中使用JPA(7)密码和BCrypt
- Java for Web学习笔记(六七):Service和Repository(2)抽象分层例子
- Java for Web学习笔记(一零三):Spring框架中使用JPA(3)JPA仓库
- Java for Web学习笔记(八五):SOAP(2)小例子
- Java for Web学习笔记(一零五):Spring框架中使用JPA(5)Isolation和C3P0(上)
- Java for Web学习笔记(八四):SOAP(1)小例子准备
- Java for Web学习笔记(五六):Spring框架简介(5)自动识别
- Java for Web学习笔记(二三):EL(3)EL的视图
- Java for Web学习笔记(五七):Spring框架简介(6)代码设置
- Java for Web学习笔记(四六):WebSocket(3)Java Server
- Java for Web学习笔记(二五):JSTL(1)使用JSTL
- Java for Web学习笔记(四十):Filter(2)AsyncContext和Filter
- Java for Web学习笔记(四七):WebSocket(4)Java Client和二进制消息
- Java for Web学习笔记(十):Servlet(8)下发文件
- Java for Web学习笔记(五一):Log(3)代码中使用log4j2
- Java for Web学习笔记(四四):WebSocket(1)演化历程
- Java for Web学习笔记(二一):EL(1)什么是EL
- Java for Web学习笔记(四一):Filter(3)用于Log
- Java for Web学习笔记(三八):自定义tag(6)一些注意