# Spring and Hibernate & JPA ## 封裝永續性邏輯 Before: ```mermaid flowchart LR; subgraph 程式碼A A[實作永續性程式碼(不可重用)] end subgraph 程式碼B B[實作永續性程式碼(不可重用)] end ``` After: ```mermaid flowchart LR; A[程式碼A] B[程式碼B] C[實作永續性程式碼(可重用)] A-->C B-->C ``` ## 永續性邏輯封裝進化 ### 暴力法 - 適合小型專案或原型開發專案 - 使用JDBC直接操作資料庫以取得、更新與儲存資料庫的資料,並把存取邏輯直接寫入領域物件(Domain Object) 中。 - 開發者需要重複撰寫繁雜跟類似的JDBC呼叫 - **資料邏輯跟領域物件邏輯混雜** ### DAO - DAO在Java EE被視為**標準作法** - 主要精神在於將資料庫相關的程式碼抽出並置於DAO物件裡 - 僅從架構上定義資料存取邏輯封裝,並未具體說明詳細機制,通常可以跟其他封裝方式混用 ### 永續性服務(Persistence Service) - 主要被 \.NET framework採用作為跨平台的永續性機制 - 主要精神:使用XML來表達永續性資料,並作為共通格式 ### 永續性框架(Persistence Framework) - 別名:ORM工具(O-R Mapping) - 自動將資料表對應到類別,Table中每一筆資料對應到一個類別實體。每一個column中的值就是這個實體的屬性值(properties) - 永續性框架的開發難度高 - 物件導向語言透過定義物件、行為及相互操作關係建構系統;而關聯式資料庫則透過資料表與資料表間的關聯性描述資料。 - 如何表達物件導向的繼承、多型等概念? - 如何將多個Table某些屬性合成一個類別?(Join) - 如何必須能夠支援多整資料庫的交易機制? - 因為開發困難,直接採用成熟的永續性框架來使用吧! - Hibernate - iBATIS - TopLink - JDO (Java Data Objects) - JPA (Java Persistence API) ## O-R mapping發展史 ```mermaid flowchart LR; DB[(DB)] JDBC U[Persistence Unit] EB[EntityBean] HB[Hibernet] JPA[JPA] NB[Net Beans Code Generation] SD[Spring Data] IB[IBatis] subgraph EJB container EB end EB-->JDBC-->DB EB-->U-->JDBC HB-->JDBC TopLink-->JDBC JDO-->JDBC JPA-->HB & TopLink & JDO NB-->JPA SD--SpingData-with-JPA-->JPA SD--SpingData-with-JDBC-->JDBC IB--->JDBC ``` 1. Java EE 一開始在EJB上推出Entity Bean,用xml紀錄OR關係。但複雜、效能不好,沒有人用。 2. Hibernate: Garvin King抨擊Entity Bean難用,自己提出O-R mapping framework - Runtime Code Gen: 自行產生SQL 3. TopLink:Oracle推出 - Runtime Code Gen: 自行產生SQL 4. JDO: Sun工程師將Persistence Unit從EJB獨立出來並制定標準 - Bytecode Enhance: 效能比Hibernate跟TopLink好 - 但遭到Java EE 3大廠商投反對票,因為在Java SE可以免費使用 - 還是有開發者實作=>KODO 5. Java EE 推出JPA - 找 Garvin King幫忙設計 - 提供抽象介面,方便切換底層O-R mapping (像是Hibernate, TopLink, JDO 都有實作) >如果想保持專案彈性而且無特殊需求,可以選擇JPA。之後抽換底層實作不需整個砍掉重練 ## Hibernate & JPA 開發人員可以選擇只使用JPA以確保最大相容性,或是透過Hibernate API實作更進階功能,也可以混合使用。 **但如果未來有可能會切換底層最好使用JPA** <table col> <tr> <td>JPA</td> <td>Hibernate API</td> </tr> <tr> <td>Hibernate Annotation(JPA)<br/>Hibernate EntityManager</td> <td>Hibernate Annotation(Hibernate)<br/>Hibernate XML對映檔</td> </tr> <tr> <td colspan=2 style="text-align:center">Hibernate Core</td> </tr> </table> ## Hibernate - 最早由Gavin King主導開發(嫌棄Entity Bean乾脆自己做一個),以開放原始碼專案發展 - 優勢: - 支援幾乎所有形式的物件-關聯式對應(ORM) - 自動產生的SQL品質相當良好(**runtime時期codegen**) - 效能高(但因為是runtime時期codegen,效能比不上JDO) - Spring+Hibernate儼然已成為資料庫邏輯標準形式(廠商應該會用) - Sping 透過DAO方式對Hibernate提供封裝跟API支援 ## Hibernate的核心與支援模組 - Hibernate Core: 包含ORM機制的API以及XML為主的ORM處理支援 - Hibernate Annotations:在JDK 5.0上執行程式可以用Annotation 設定ORM - Hibernate EntityManager:主要提供與EJB 3/JPA的相容性 - Hibernate Tool:提供eclipse及Ant相關工具 - Seam:支援Java EE5的應用程式框架,提供JSF,EJB 3.0與Hibernate整合解法(對應Struts+Spring+Hibernate) ## ORM 描述檔案 使用Hibernatr時,必須為每一個Domain Object做一個ORM描述檔。 - 慣例會用「類別名稱.hbm.xml」命名。 ```mermaid flowchart LR; H[Hibernate] App[應用程式] DB[(DB)] P[Product] X[Product.hbm.xml] T[[TB_PRODUCT]] App <-.-> H <-.->DB P <-.-> X<-.->T ``` - 慣例上和類別原始檔放同一層目錄,方便管理 ## Hibernate應用程式開發流程 1. 準備函式庫的Jar檔:把Hibernate相關jar檔加入Classpath 2. 製作ORM檔案:針對每一個要做ORM的Domain Object寫一個hbm.xml 3. 準備hibernate設定檔:撰寫hibernate.cfg.xml並放在**原始碼的根目錄** 4. 建立configuration:建構Configurarion物件,呼叫configure()讀入hibernate.cfg.xml ## Hibernate 核心類別 ![](https://media.geeksforgeeks.org/wp-content/uploads/HBArchi.png) >固定流程:Spring提供Hibernate Template減少code,但需要注意版本相依性 ## ORM檔結構 eclipse有工具可以自行產生檔案: 1. 從現成Table產生ORM檔跟類別 2. 從類別,ORM檔產生table schema ```xml= <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <!--設定ORM--> <class name="com.geeksforgeeks.GeekUserDetails" table="GeekUserDetails"> <!--指定PK,generator class=native 表示是由DB自行產生--> <id name="geekUserId" type="int" column="geekUserId"> <generator class="native"/> </id> <property name="geekUsername"> <column name="geekUsername"/> </property> <property name="numberOfPosts" type = "int"> <column name="numberOfPosts"/> </property> <property name="createdBy"> <column name="CREATED_BY"/> </property> <property name="createdDate" type="date"> <column name="CREATED_DATE"/> </property> </class> </hibernate-mapping> ``` ```java= import java.util.Date; public class GeekUserDetails { private int geekUserId; private String geekUsername; private int numberOfPosts; public int getNumberOfPosts() { return numberOfPosts; } public int getGeekUserId() { return geekUserId; } public void setGeekUserId(int geekUserId) { this.geekUserId = geekUserId; } public String getGeekUsername() { return geekUsername; } public void setGeekUsername(String geekUsername) { this.geekUsername = geekUsername; } public void setNumberOfPosts(int numberOfPosts) { this.numberOfPosts = numberOfPosts; } private String createdBy; private Date createdDate; public String getCreatedBy() { return createdBy; } public void setCreatedBy(String createdBy) { this.createdBy = createdBy; } public Date getCreatedDate() { return createdDate; } public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; } } ``` ### 使用Annotations (JPA做法) ```java= @Entity @Table(name="GeekUserDetails") public class GeekUserDetails { @Id @GeneratedValue(strategy=GenerationType.TABLE) @Column(name="geekUserId") private int geekUserId; @Column(name="geekUsername") private String geekUsername; @Column(name="numberOfPosts") private int numberOfPosts; @Column(name="CREATED_BY") private String createdBy; @Column(name="CREATED_DATE") private Date createdDate; ... } ``` ## Hibernate 設定檔結構 show_sql 和format_sql會在console印出sql方便debug ```xml <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <!-- Main configuration file --> <hibernate-configuration> <session-factory> <!-- As we are connecting MySQL, com.mysql.jdbc. Driver is required(JDBC driver class) --> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <!-- Here geeksforgeeks is the name of the database that we are connecting(JDBC URL) --> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/geeksforgeeks</property> <!-- Username to connect to MySQL --> <property name="hibernate.connection.username">root</property> <!-- Password to connect to MySQL, Provide your correct password --> <property name="hibernate.connection.password">XXXX</property> <!-- Dialect required between hibernate and MySQL --> <!-- This property makes Hibernate generate the appropriate SQL for MySQL here --> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="show_sql">true</property> <property name="format_sql">true</property> <property name="hbm2ddl.auto">update </property> <!-- We need to provide the exact mapping file which we have created earlier --> <mapping resource="geekuser.hbm.xml" /> <!-- Annotation --> <!-- <mapping class="GeekUserDetails" /> --> </session-factory> </hibernate-configuration> ``` ## 建立Session ```java! Configuration config = new Configuration(); config.configure(); //載入hibernate.cfg.xml //取得SessionFactory SessionFactory sessionFactory = config.buildSessionFactory(); //取得session Session session = sessionFactory.openSession(); //開始交易 session.beginTransaction(); Product product = new Product(); product.setName("Cookies"); product.setPrice(200); session.save(product); //結束交易 session.getTransaction().commit(); ``` ## 查詢資料 - HQL: Hibernate自己的語法,執行時會自動產生最佳化native sql ```java Query query = session.createQuery("from Product p where p.name like '%cat%'"); List list = query.list(); ``` - Criteria:短字串更加減少typo出錯機率 ```java Criteria cirteria = session.createCriteria(Product.class).add(Restrictions.like("name","%cat%")); List list = criteria.list(); ``` - Native SQL (儘量避免,有可能會寫到特殊語法之後移轉其他DB會需要重寫) ```java session.CreateNativeQuery("select * from TB_PRODUCT where name like '%cat%'"); ``` ## Spring對Hibernate封裝 ## SessionFactoryBean ## SessionFactoryBean(Annotation) ## 使用Spring整合Hibernate跟DAO >Programing by Interface:通常會發生在跨層(tier, layer) 系統切割技巧 ## 實作注意 ### 版本選擇 如果要使用Spring提供的樣板,需要注意Spring framework 5.15版要搭配Hibernate 5.0 ### Hibernate template 錯誤訊息 ``` Exception in thread "main" org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition. at org.springframework.orm.hibernate5.HibernateTemplate.checkWriteOperationAllowed(HibernateTemplate.java:1094) at org.springframework.orm.hibernate5.HibernateTemplate.lambda$saveOrUpdate$15(HibernateTemplate.java:690) at org.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateTemplate.java:384) at org.springframework.orm.hibernate5.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:350) at org.springframework.orm.hibernate5.HibernateTemplate.saveOrUpdate(HibernateTemplate.java:689) at lab5.ex1.ProductDaoHibernateImpl.save(ProductDaoHibernateImpl.java:28) at lab5.ex1.Client.main(Client.java:20) ``` 因為以前Hibernate樣板有包含交易控制,但新版移除由使用者自行處理 所以需要新增transaction manager跟annotation