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的代碼有重複,可以採用下面的方式:
@Embeddablepublic 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相關文章