MyBatis+Hibernate+JDBC對比分析
MyBatis目前作為持久層,用的最多,因為它符合網際網路開發的變動性,實際開發中需求總會有這樣的,那樣的變動,MyBatis雖然沒有Hibernate那麼全自動化,而且對於開發人員的sql能力要求比較高,但是正是因為這樣sql可以優化,業務也更容易擴充套件。
hibernate對於sql能力不是特別強的開發人員,是一個很好的利器,而且hibernate更面向物件,hibernate不適合開發高併發高訪問量的應用,很簡單不能sql優化,每次查詢基本都要全表掃描,即便是有hql,但是過多的使用hql,反而破壞Hibernate的封裝性,實際開發多聯表查多個欄位資訊,有的時候一張表十幾個欄位,資訊,另外一個表十幾個,在另外一個表又十幾個,對於Hibernate而言是個不小的挑戰,對於效能要求不高,響應速度較慢的應用,同一時間段訪問人不多的應用,還是很不錯的,可以大幅度提高開發效率
OA辦公,ERP等應用比較適合用Hibernate開發
像入口網站,部落格這樣的,訪問量比較大,不只是前端優化,後臺也要優化,所謂的後臺主要是指sql優化,當然還有Java程式碼方面
MyBatis相對於JDBC而言,要好多了,至少它實現瞭解耦,sql語句和Java程式碼分離。
對於目前我個人部落格專案,它的逆向工程讓我不再重複單表的增刪改查,節省比較多的時間,當然mybatis,雖然對於需求變動大的專案而言是比較符合實際的需要的,但是它的不好之處也顯現出來了,就是對於不同的資料庫需要書寫不同的sql語句,可移植性差,而且它的外掛用不好的話,導致效能也會下降
閒話不多說,貼一波個人編寫的程式碼,溫故而知新吧
我這邊演示是JDK8+MAVEN工程
pom檔案內容如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.example.test</groupId> <artifactId>mybatis</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <!-- hibernate核心包 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.12.Final</version> </dependency> <!--mybatis核心包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.1.1</version> </dependency> <!-- 連線Mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.21</version> </dependency> <!--列印日誌 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> </project>
一、JDBC示例
package cn.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; public class JdbcExample { private static Connection getConnection() throws Exception { Connection conn = null; try { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/ssm"; String username = "root"; String password = "1234"; conn = DriverManager.getConnection(url, username, password); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); return null; } return conn; } public User getUserById(Integer Id) throws Exception { /** * 頻繁的建立連線 */ Connection connection = getConnection(); PreparedStatement ps = null; ResultSet rs = null; try { /** * Java程式碼和sql混合,不解耦,隨著專案擴大,工作量會成倍增加 */ ps = connection.prepareStatement("select * from `user` where id=?"); ps.setInt(1,Id); rs = ps.executeQuery(); while(rs.next()) { /** * 指定獲取的屬性 * 實際開發中資料表的欄位多,想要獲取十幾二十幾個欄位資訊,如下rs.getInt("欄位名")等等一系列不知要寫多少個,隨著程式碼量的增大,更不利於排錯 */ Integer id = rs.getInt("id"); String userName = rs.getString("user_name"); User user = new User(); user.setId(id); user.setUserName(userName); return user; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { /** * 頻繁的關閉連線 */ this.closeAll(rs, ps, connection); } return null; } private void closeAll(ResultSet rs,PreparedStatement ps,Connection conn) { try { if(rs!=null && rs.isClosed()) { rs.close(); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } try { if(ps!=null && ps.isClosed()) { ps.close(); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } try { if(conn!=null && conn.isClosed()) { conn.close(); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } public static void main(String[] args) throws Exception { JdbcExample jdbc = new JdbcExample(); User user = jdbc.getUserById(1); System.out.println(user.getUserName()); } /** * 從上述程式碼我們可以總結出Java連線JDBC的步驟 * 1.載入驅動 * 2.建立連線 * 3.通過PreparedStatement執行sql * 4.由ResultSet返回結果集 * 5.關閉連線 * * 使用傳統的JDBC,存在如下問題: * 1.不解耦,JDBC和sql混合一起,當需求變動,既要改Java程式碼,又要修改sql語句,工作量十分大,而在實際專案中,需求是不斷變化的 * 2.頻繁的建立連線,關閉連線,雖然後面有連線池,或者是將其封裝成一個Utils,不過程式碼量依然會很大 * * 說明:實際開發中並不用它,但是作為開發者必須要知道技術的應用場景和利弊,這樣才能在開發中遊刃有餘 */ }
JavaBean
說明:JavaBean在該例子中是適用所有的
資料表字段也就兩個屬性,大家自己建即可,這裡就不貼了
package cn.jdbc; public class User { private Integer Id; private String userName; public Integer getId() { return Id; } public void setId(Integer id) { Id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }
二、Hibernate示例
1.hibernate.cfg.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"> <hibernate-configuration> <session-factory> <!-- property 元素用於配置Hibernate中的屬性 鍵:值 --> <!-- hibernate.connection.driver_class : 連線資料庫的驅動 --> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <!-- hibernate.connection.username : 連線資料庫的使用者名稱 --> <property name="hibernate.connection.username">root</property> <!-- hibernate.connection.password : 連線資料庫的密碼 --> <property name="hibernate.connection.password">1234</property> <!-- hibernate.connection.url : 連線資料庫的地址,路徑 --> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/ssm</property> <!-- show_sql: 操作資料庫時,會 向控制檯列印sql語句 --> <property name="show_sql">true</property> <!-- format_sql: 列印sql語句前,會將sql語句先格式化 --> <property name="format_sql">true</property> <!-- hbm2ddl.auto: 生成表結構的策略配置 update(最常用的取值): 如果當前資料庫中不存在表結構,那麼自動建立表結構. 如果存在表結構,並且表結構與實體一致,那麼不做修改 如果存在表結構,並且表結構與實體不一致,那麼會修改表結構.會保留原有列. create(很少):無論是否存在表結構.每次啟動Hibernate都會重新建立表結構.(資料會丟失) create-drop(極少): 無論是否存在表結構.每次啟動Hibernate都會重新建立表結構.每次Hibernate執行結束時,刪除表結構. validate(很少):不會自動建立表結構.也不會自動維護表結構.Hibernate只校驗表結構. 如果表結構不一致將會丟擲異常. --> <property name="hbm2ddl.auto">update</property> <!-- 資料庫方言配置 org.hibernate.dialect.MySQLDialect (選擇最短的) --> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <!-- hibernate.connection.autocommit: 事務自動提交 --> <property name="hibernate.connection.autocommit">true</property> <!-- 將Session與執行緒繫結=> 只有配置了該配置,才能使用getCurrentSession --> <property name="hibernate.current_session_context_class">thread</property> <!-- 引入ORM 對映檔案 填寫src之後的路徑 --> <mapping resource="cn/hibernate/User.hbm.xml"/> </session-factory> </hibernate-configuration>
2.User.hbm.xml檔案
<hibernate-mapping> <!-- <class>:使用class元素定義一個持久化類。 name="cn.javass.user.vo.UserModel":持久化類的java全限定名; table="tbl_user":對應資料庫表名; mutable="true":預設為true,設定為false時則不可以被應用程式更新或刪除; dynamic-insert="false":預設為false,動態修改那些有改變過的欄位,而不用修改所有欄位; dynamic-update="false":預設為false,動態插入非空值欄位; select-before-update="false":預設為false,在修改之前先做一次查詢,與使用者的值進行對比,有變化都會真正更新; --> <class name="cn.hibernate.User" table="user" dynamic-insert="true" dynamic-update="true"> <!-- <id>:定義了該屬性到資料庫表主鍵欄位的對映。 name="userId":標識屬性的名字; column="userId":表主鍵欄位的名字,如果不填寫與name一樣; --> <id name="Id"> <!-- <generator>:指定主鍵由什麼生成,推薦使用uuid(隨機生成唯一通用的表示符,實體類的ID必須是String), native(讓資料庫自動選擇用什麼生成(根據底層資料庫的能力選擇identity,sequence或hilo中的一種)), assigned(指使用者手工填入,預設)。 --> <generator class="native"/> </id> <!-- <property>:為類定義一個持久化的javaBean風格的屬性。 name="name":標識屬性的名字,以小寫字母開頭; column="name":表主鍵欄位的名字,如果不填寫與name一樣; update="true"/insert="true":預設為true,表示可以被更新或插入; --> <property name="userName" column="user_name" /> </class> </hibernate-mapping>
3.HibernateExample.java
package cn.hibernate; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class HibernateExample { private static SessionFactory sessionFactory; static { try { Configuration cfg = new Configuration().configure("hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } public static SessionFactory getSessionFactory() { return sessionFactory; } }
4.TestHibernate.java
package cn.hibernate; import org.hibernate.Session; public class TestHibertnate { /** * hibernate流程 *1.通過Configuration建立SessionFactory *2.通過SessionFactory建立Session *3.通過Session開啟openSession *4.通過session呼叫方法 * * *openSession和getCurrentSession的區別 *openSession建立session時autoCloseSessionEnabled引數為false,即在事物結束後不會自動關閉session,需要手動關閉,如果不關閉將導致session關聯的資料庫連線無法釋放,最後資源耗盡而使程式掛掉。 *getCurrentSession建立session時autoCloseSessionEnabled,flushBeforeCompletionEnabled都為true,並且session會同sessionFactory組成一個map以sessionFactory為主鍵繫結到當前執行緒。 * @param args */ public static void main(String[] args) { Session session = null; try { session = HibernateExample.getSessionFactory().openSession(); User user = session.get(User.class, 1); System.out.println(user.getUserName()); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); }finally { if(session!=null) { session.close(); } } } }
三、MyBatis示例
1.MyBatisExample示例
package cn.mybatis; import java.io.IOException; import java.io.InputStream; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class MyBatisExample { private static SqlSessionFactory sqlSessionFactory = null; public static SqlSessionFactory getSqlSessionFactory() { InputStream is = null; if(sqlSessionFactory==null) { String resource = "mybatis-config.xml"; try { sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(resource)); return sqlSessionFactory; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return sqlSessionFactory; } }
2.TestMyBatis.java
package cn.mybatis; import org.apache.ibatis.session.SqlSession; public class TestMyBatis { public static void main(String[] args) { SqlSession sqlSession = null; sqlSession = MyBatisExample.getSqlSessionFactory().openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.getUserById(1); /** * 通過Main方法執行只能顯示ID資訊,不能顯示userName的資訊 * 這是因為resultType針對的欄位和屬性一致的情況,當資料表字段與JavaBean屬性不一致時,要得到資料,需要使用resultMap */ System.out.println(user.getId()+"||"+user.getUserName()); } }
3.UserMapper.java
package cn.mybatis; public interface UserMapper { User getUserById(Integer Id); }
4.UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.mybatis.UserMapper"> <!-- id 必須與對應介面方法一致 parameterType 引數型別可以是Map,String,Integer等等一大堆,但是必須要指定 resultType 適用場景:資料表字段和JavaBean實體屬性一致 resultMap 適用場景:資料表字段和JavaBean實體屬性不一致 --> <!-- <select id="getUserById" parameterType="Integer" resultType="cn.mybatis.User"> select id,user_name from `user` where id=#{Id} </select> --> <select id="getUserById" parameterType="Integer" resultMap="users"> select id,user_name from `user` where id=#{Id} </select> <!-- column對應資料表字段 property對應JavaBean屬性 type 通常指返回型別 一般要寫全限定名 不過只要在mybatis-config.xml配置別名掃描就不要寫全名了 --> <resultMap type="User" id="users"> <id column="id" property="Id"/> <result column="user_name" property="userName"/> </resultMap> </mapper>
5.mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 給cn.mybatis包下起別名 --> <typeAliases> <package name="cn.mybatis" /> </typeAliases> <!-- 對事務的管理和連線池的配置 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/ssm" /> <property name="username" value="root" /> <property name="password" value="1234" /> </dataSource> </environment> </environments> <!-- mapping 檔案路徑配置 --> <mappers> <mapper resource="cn/mybatis/UserMapper.xml" /> </mappers> </configuration>
MyBatis還有很多有待研究的,說到這,不得不提一個增強版本的MyBatis 稱之為MyBatis Plus 可無縫結合Spring+SpringMVC,而且增刪改查也不需要寫,只需繼承一個類即可
參考官網:http://mp.baomidou.com/
關於mybatis的逆向工程和動態代理,可以參考我的部落格Java框架和MyBatis