對象關係映射:Object-Relational Mapper(O/RM)
我們希望直接在關係型資料庫中讀寫對象,而無需在代碼中操作SQL,這需要Object-Relational Mapper。
我們可能有幾十個表格,每個表格都有很多列,這樣整個增刪改查的代碼就很繁瑣,使用O/RM,我們的代碼可以得到很大的簡化。仍如使用上一學習中的學生例子:
public Product getStudent(long id){ return this.getCurrentTransaction().get(Student.class, id);}public void addStudent(Student student){ this.getCurrentTransaction().persist(student);}public void updateStudent(Student student){ this.getCurrentTransaction().update(student);}public void deleteStudent(Student student){ this.getCurrentTransaction().delete(student);}
我們需告知O/RM映射的規則,不同的O/RM有不同映射方式,存在遷移的成本。高度的抽象會隱藏實現的細節,有時合理的statement(sql)對效能很關鍵,我們可以通過資料庫服務的statement log來分析。很多O/RM可以根據映射指令自動產生表格的schema(自動產生表格),但我們不應該這樣做,尤其在生產環境或者產品,要記住我們應該自己設計表格。如果表格設計差,是你的錯,不是O/RM。 Java Persistence API
O/RM的產品有很多,例如Top Link及其後來的Eclipse Link,iBATIS以及後來的MyBatis,Hibernate ORM以及用於.NET的NHibernate。為解決不同O/RM遷移的成本問題,出現了Java Persistence API,並作為JSR 220加入了在Java EE 5。在2009年,JPA 2.0作為JSR 317加入了Java EE 6,在2013年,JPA2.1作為JSR 338加入Jaav EE 7。
JPA使用JDBC來執行SQL statement,但封裝得很好,我們不會直接使用JDBC API,也避免直接使用SQL語句。 Hibernate ORM
Hibernate是JPA的實現。在maven中,scope設定為runtime,即不直接使用其API,而是使用JPA。但是Hibernate提供了JPA外的一些能力,如果需要使用,則應將scope設定為compile。下面重點介紹這些額外的功能,這些功能僅作初步的瞭解,在後面的學習中將不再涉及。 使用Hibernate Mapping檔案
早期的Hibernate版本使用XML檔案來描述映射,在3.5版本支援annotation,包括JPA的標記。現在也可以使用XML來映射,但應只在僅使用Hibernate而非JPA的情境。
一般來講,XML對應檔放在resource,與對應的entity同名,且路徑相同,例如一個com.example.entities.Product的類對應為Product.hbm.xml,位於resource下的com/example/entities/Product.hbm.xml。例子如下:
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping> <class name="com.example.entities.Product" table="Product" schema="dbo"> <!-- id用於表示單列單field的index,如果多列或者組合,使用composite-id --> <id name="productId" column="ProductId" type="long" unsaved-value="0"> <generator class="identity" /> </id> <!-- property將類的field映射到表的列,此外還有map,list,set,或者one-to-many和many-to-one等進行entities的關聯。具體的可以看Hibernate ORM的文檔,我們在後面並不適用xml的方式來映射,僅作為介紹 --> <property name="name" column="Name" type="string" length="60" /> <property name="description" column="Description" type="string" length="255" /> <property name="datePosted" column="DatePosted" type="java.time.Instant" /> <property name="purchases" column="Purchases" type="long" /> <property name="price" column="Price" type="double" /> <property name="bulkPrice" column="BulkPrice" type="double" /> <property name="minimumUnits" column="MinimumUnits" type="int" /> <property name="sku" column="Sku" type="string" length="12" /> <property name="editorsReview" column="EditorsReview" type="string" length="2000" /> </class></hibernate-mapping>
session API
Hibernate Session是執行transaction的單位,和JPA的EntityManager很是相似。 事務是一種機制、是一種操作序列,它包含了一組資料庫操作命令,這組命令要麼全部執行,要麼全部不執行。因此事務是一個不可分割的工作邏輯單元。在資料庫系統上執行並行作業時事務是作為最小的控制單元來使用的。這特別適用於多使用者同時操作的資料通訊系統。事務4大屬性:
1 原子性(Atomicity):事務是一個完整的操作。
2 一致性(Consistency):當事務完成時,資料必須處於一致狀態。
3 隔離性(Isolation):對資料進行修改的所有並發事務是彼此隔離的。
4 持久性(Durability):事務完成後,它對於系統的影響是永久性的。[1]
下面是有關的API:
//【Read】通過surrogate key擷取entityreturn (Product)session.get(Product.class, id);//【Insert】增加entity,並返回產生的ID。如果已經存在ID,會更新這個ID。session.save(product);/*【Insert】同樣是增加entity,並且更為安全。當事務關閉時,persist()不會執行INSERT(SQL)操作,而save()會開啟一個額外的transaction來進行。save()可以馬上獲得ID,而persist()不保證一定執行,要在後面加上flush()才保證馬上執行,因此它不會返回自動產生的ID。我們可以在後面加入flush()來擷取。flush()將使當前的掛起的statement馬上執行,但並不會關閉session。flush()還和Hibernate的statement隊列有關,一個事務裡面有多個動作,但這些動作的自行不一定是我們以為的順序。在flush時候,自上個flush()後的執行順序為:* 1、按順序的entity insert操作 * 2、按順序的entity update操作* 3、按順序的collection的delete操作* 4、按順序的collection element的insert,update和delete操作* 5、按順序的collection的insert操作* 6、按涮新的entity delete操作* 通過flush()我們可以確保我們期待的順序,需要注意save()是馬上執行的,和flush()無關。我們使用flush()要非常小心,可能會引發混亂*/session.persist(product);session.flush();//【Update】udpate()的用法有些特別,不允許session中,之前有過對entity有操作,即之前不對進行get,save,persist,否則或拋出異常session.update(product);//【Update】merge沒有session中之前是否對entity有操作的限制,因此是更為常用的方式session.merge(product);//【Delete】session.delete(product); //【Evict】驅逐:從session中將entity驅逐或者分離,但對資料庫沒有實際影響。如果此時entity有尚未執行的statement,將被取消,不會再執行。如果我們需要驅逐所有的entity,使用clear()session.envict(product);session.clear();
使用surrogate key來查詢當然不是唯一的方位,表格可以有其他的key,不是都要通過primary key的。可以通過org.hibernate.Criteria API或者 org.hibernate.Query API,下面是等價的
return (Product)session.createCriteria(Product.class) .add(Restrictions.eq("sku", sku)) .uniqueResult();//裡面的語句是HQL,類似於SQL,但用的是對象的概念,而不是表格,列。return (Product)session.createQuery("FROM Product p WHERE p.sku = :sku") .setString("sku", sku) .uniqueResult();下面是返回名字首碼是java,時間一年前的例子:
return (List<Product>)session.createCriteria(Product.class) .add(Restrictions.gt("datePosted",Instant.now().minus(365L, ChronoUnit.DAYS))) .add(Restrictions.ilike("name", "java", MatchMode.START)) .addOrder() .list();return (List<Product>)session.createQuery("FROM Product p WHERE datePosted > :oneYearAgo AND name ILIKE :nameLike ORDER BY name") .setParameter("oneYearAgo",Instant.now().minus(365L, ChronoUnit.DAYS)) .setString("nameLike", "java%") .list();
sessionfactory Session需要附著在JDBC的connection上面,SessionFactory提供了session擷取的相關操作:
//開啟新的session,採用預設配置(DataSource,interceptors等)Session session = sessionFactory.openSession();//開啟新的session,採用指定設定Session session = sessionFactory.withOptions() .connection(connection).openSession();//開啟新的session,攔截所有的SQL語句並進行修改,例如設定schema="@SCHEMA@"Session session = sessionFactory.withOptions() .interceptor(new EmptyInterceptor() { @Override public String onPrepareStatement(String sql){ return sql.replace("@SCHEMA@", schema); } }).openSession();Hibernate ORM可以有stateless session,特別適合於大量資料的操作,我的理解,就是非事務性。開啟無狀態的session,可以通過 .openStatelessSession()開啟預設的額無狀態session或者通過 .withStatelessOptions()開啟定製的無狀態會話。無狀態session是不支援獲得current session的。
//SessionFactory是Thread-safe,將返回當前線程中,之前開啟的session。Session session = sessionFactory.getCurrentSession();
在Spring Framework中建立SessionFactory Bean
Spring framework提供SessionFactory可以很好地管理Session的建立和關閉,避免資源訪問出現記憶體流失。
對於XML方式,定義org.springframework.orm.hibernate5.LocalSessionFactoryBean,將返回SessionFactory.LocalSessionFactoryBean,同時實現org.springframework.dao.support.PersistenceExceptionTranslator介面,即將Hibernate的ORM異常轉換為Spring framework通用的持久化異常。
XML配置如下[2]:
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="annotatedClasses" ref="hibernateClasses" /> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect"> ${hibernate.dialect} </prop> <prop key="hibernate.show_sql"> ${hibernate.show_sql} </prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.generate_statistics"> ${hibernate.generate_statistics} </prop> <prop key="hibernate.hbm2ddl.auto"> ${hibernate.hbm2ddl.auto} </prop> </props> </property></bean><bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /></bean>Java代碼配置如下,在root上下文中配置
@EnableTransactionManagementpublic class RootContextConfiguration implements AsyncConfigurer, SchedulingConfigurer{ ... @Bean public PersistenceExceptionTranslator persistenceExceptionTranslator(){ return new HibernateExceptionTranslator(); } @Bean public HibernateTransactionManager transactionManager(){ HibernateTransactionManager manager = new HibernateTransactionManager(); manager.setSessionFactory(this.sessionFactory()); return manager; } @Bean public SessionFactory sessionFactory(){ LocalSessionFactoryBuilder builder = new LocalSessionFactoryBuilder(this.dataSource()); builder.scanPackages("com.wrox.entities"); builder.setProperty("hibernate.default_schema", "dbo"); builder.setProperty("hibernate.dialect",MySQL5InnoDBDialect.class.getCanonicalName()); return builder.buildSessionFactory(); }} 相關連結: 我的Professional Java for Web Applications相關文章