在Java 程序裏面去鏈接數據庫,最原始的辦法是使用JDBC 的API。咱們先來回顧一下使用JDBC 的方式,咱們是怎麼操做數據庫的。java
// 註冊JDBC 驅動 Class.forName("com.mysql.jdbc.Driver"); // 打開鏈接 conn = DriverManager.getConnection(DB_URL, USER, PASSWORD); // 執行查詢 stmt = conn.createStatement(); String sql= "SELECT bid, name, author_id FROM blog"; ResultSet rs = stmt.executeQuery(sql); // 獲取結果集 while(rs.next()){ int bid = rs.getInt("bid"); String name = rs.getString("name"); String authorId = rs.getString("author_id"); }
首先,咱們在maven 中引入MySQL 驅動的依賴(JDBC 的包在java.sql 中)。mysql
在每一段這樣的代碼裏面,咱們都須要本身去管理數據庫的鏈接資源,若是忘記寫close()了,就可能會形成數據庫服務鏈接耗盡。另外還有一個問題就是處理業務邏輯和處理數據的代碼是耦合在一塊兒的。若是業務流程複雜,跟數據庫的交互次數多,耦合在代碼裏面的SQL 語句就會很是多。sql
若是要修改業務邏輯,或者修改數據庫環境(由於不一樣的數據庫SQL 語法略有不一樣),這個工做量是也是難以估計的。還有就是對於結果集的處理,咱們要把ResultSet 轉換成POJO 的時候,必須根據字段屬性的類型一個個地去處理,寫這樣的代碼是很是枯燥的:數據庫
int bid = rs.getInt("bid"); String name = rs.getString("name"); String authorId = rs.getString("author_id"); blog.setAuthorId(authorId); blog.setBid(bid); blog.setName(name);
也正是由於這樣,咱們在實際工做中是比較少直接使用JDBC 的。那麼咱們在Java程序裏面有哪些更加簡單的操做數據庫的方式呢? apache
https://commons.apache.org/proper/commons-dbutils/數組
DbUtils 解決的最核心的問題就是結果集的映射, 能夠把ResultSet 封裝成JavaBean。它是怎麼作的呢?首先DbUtils 提供了一個QueryRunner 類,它對數據庫的增刪改查的方法進行了封裝,那麼咱們操做數據庫就能夠直接使用它提供的方法。在QueryRunner 的構造函數裏面,咱們又能夠傳入一個數據源,好比在這裏咱們Hikari,這樣咱們就不須要再去寫各類建立和釋放鏈接的代碼了。緩存
public class HikariUtil { private static final String PROPERTY_PATH = "/hikari.properties"; private static final Logger LOGGER = LoggerFactory.getLogger(HikariUtil.class); private static HikariDataSource dataSource; private static QueryRunner queryRunner; public static void init() { HikariConfig config = new HikariConfig(PROPERTY_PATH); dataSource = new HikariDataSource(config); queryRunner = new QueryRunner(dataSource); } public static QueryRunner getQueryRunner() { check(); return queryRunner; } public static Connection getConnection() { check(); try { Connection connection = dataSource.getConnection(); return connection; } catch (SQLException e) { throw new RuntimeException(e); } } public static void close(Connection connection) { try { if (connection != null && !connection.isClosed()) { connection.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } private static void check() { if (dataSource == null || queryRunner == null) { throw new RuntimeException("DataSource has not been init"); } } }
數據源配置:session
dataSource.user=root dataSource.password=123456 jdbcUrl=jdbc:mysql://localhost:3306/gp-mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC dataSource.cachePrepStmts=true dataSource.prepStmtCacheSize=250 dataSource.prepStmtCacheSqlLimit=2048 dataSource.useServerPrepStmts=true dataSource.useLocalSessionState=true dataSource.rewriteBatchedStatements=true dataSource.cacheResultSetMetadata=true dataSource.cacheServerConfiguration=true dataSource.elideSetAutoCommits=true dataSource.maintainTimeStats=false dataSource.minimumIdle=10 dataSource.maximumPoolSize=30
那咱們怎麼把結果集轉換成對象呢?好比實體類Bean 或者List 或者Map?在DbUtils 裏面提供了一系列的支持泛型的ResultSetHandler。咱們只要在DAO 層調用QueryRunner 的查詢方法,傳入這個Handler,它就能夠自動把結果集轉換成實體類Bean 或者List 或者Map。DAO層對象:mybatis
public class BlogDao { private static QueryRunner queryRunner; static { queryRunner = HikariUtil.getQueryRunner(); } // 返回單個對象,經過new BeanHandler<>(Class<?> clazz)來設置封裝 public static void selectBlog(Integer bid) throws SQLException { String sql = "select * from blog where bid = ? "; Object[] params = new Object[]{bid}; BlogDto blogDto = queryRunner.query(sql, new BeanHandler<>(BlogDto.class), params); System.out.println(blogDto); } //返回列表,經過new BeanListHandler<>(Class<?> clazz)來設置List的泛型 public static void selectList() throws SQLException { String sql = "select * from blog"; List<BlogDto> list = queryRunner.query(sql, new BeanListHandler<>(BlogDto.class)); //list.forEach(System.out::println); } }
實體:app
public class BlogDto { private Integer bid; private String name; private Integer authorId; public BlogDto() { } public Integer getBid() { return bid; } public void setBid(Integer bid) { this.bid = bid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAuthorId() { return authorId; } public void setAuthorId(Integer authorId) { this.authorId = authorId; } @Override public String toString() { return "BlogDto{" + "bid=" + bid + ", name='" + name + '\'' + ", authorId='" + authorId + '\'' + '}'; } }
沒有用過DbUtils 的同窗,能夠思考一下經過結果集到實體類的映射是怎麼實現的?也就是說,我只傳了一個實體類的類型,它怎麼知道這個類型有哪些屬性,每一個屬性是什麼類型?而後建立這個對象而且給這些字段賦值的?答案正是反射。你們也能夠去看一下源碼映證一下是否是這樣。
DbUtils 要求數據庫的字段跟對象的屬性名稱徹底一致,才能夠實現自動映射。
除了DbUtils 以外,Spring 也對原生的JDBC 進行了封裝,而且給咱們提供了一個模板方法JdbcTemplate,來簡化咱們對數據庫的操做。
看代碼:好比咱們要把結果集轉換成Employee 對象,就能夠針對一個Employee建立一個RowMapper 對象,實現RowMapper 接口,而且重寫mapRow()方法。咱們在mapRow()方法裏面完成對結果集的處理。
public class EmployeeRowMapper implements RowMapper { @Override public Object mapRow(ResultSet resultSet, int i) throws SQLException { Employee employee = new Employee(); employee.setEmpId(resultSet.getInt("emp_id")); employee.setEmpName(resultSet.getString("emp_name")); employee.setEmail(resultSet.getString("emial")); return employee; } }
在DAO 層調用的時候就能夠傳入自定義的RowMapper 類,最終返回咱們須要的類型。結果集和實體類類型的映射也是自動完成的。
public List<Employee> query(String sql){ new JdbcTemplate( new DruidDataSource()); return jdbcTemplate.query(sql,new EmployeeRowMapper()); }
經過這種方式,咱們對於結果集的處理只須要寫一次代碼,而後在每個須要映射的地方傳入這個RowMapper 就能夠了,減小了不少的重複代碼。可是仍是有問題:每個實體類對象,都須要定義一個Mapper,而後要編寫每一個字段映射的getString()、getInt 這樣的代碼,還增長了類的數量。因此有沒有辦法讓一行數據的字段,跟實體類的屬性自動對應起來,實現自動映射呢?固然,咱們確定要解決兩個問題,一個就是名稱對應的問題,從下劃線到駝峯命名;第二個是類型對應的問題,數據庫的JDBC 類型和Java 對象的類型要匹配起來。咱們能夠建立一個BaseRowMapper<T>,經過反射的方式自動獲取全部屬性,把表字段所有賦值到屬性。上面的方法就能夠改爲:
return jdbcTemplate.query(sql,new BaseRowMapper(Employee.class));
這樣,咱們在使用的時候只要傳入咱們須要轉換的類型就能夠了,不用再單首創建一個RowMapper。咱們來總結一下,DbUtils 和Spring JDBC,這兩個對JDBC 作了輕量級封裝的框架,或者說工具類裏面,都幫助咱們解決了一些問題:
可是仍是存在一些缺點:
要解決這些問題,使用這些工具類仍是不夠的,要用到咱們今天講的ORM 框架。那什麼是ORM?爲何叫ORM?ORM 的全拼是Object Relational Mapping,也就是對象與關係的映射,對象是程序裏面的對象,關係是它與數據庫裏面的數據的關係。也就是說,ORM 框架幫助咱們解決的問題是程序對象和關係型數據庫的相互映射的問題。
應該有不少同窗是用過Hibernate 或者如今還在用的。Hibernate是一個很流行的ORM 框架,2001 年的時候就出了第一個版本。在使用Hibernate 的時候,咱們須要爲實體類創建一些hbm 的xml 映射文件(或者相似於@Table 的這樣的註解)。例如:
<hibernate-mapping> <class name="cn.gupaoedu.vo.User" table="user"> <id name="id"> <generator class="native"/> </id> <property name="password"/> <property name="cellphone"/> <property name="username"/> </class> </hibernate-mapping>
而後經過Hibernate 提供(session)的增刪改查的方法來操做對象。
//建立對象 User user = new User(); user.setPassword("123456"); user.setCellphone("18166669999"); user.setUsername("qingshan"); //獲取加載配置管理類 Configuration configuration = new Configuration(); //不給參數就默認加載hibernate.cfg.xml 文件, configuration.configure(); //建立Session 工廠對象 SessionFactory factory = configuration.buildSessionFactory(); //獲得Session 對象 Session session = factory.openSession(); //使用Hibernate 操做數據庫,都要開啓事務,獲得事務對象 Transaction transaction = session.getTransaction(); //開啓事務 transaction.begin(); //把對象添加到數據庫中 session.save(user); //提交事務 transaction.commit(); //關閉Session session.close();
咱們操做對象就跟操做數據庫的數據同樣。Hibernate 的框架會自動幫咱們生成SQL語句(能夠屏蔽數據庫的差別),自動進行映射。這樣咱們的代碼變得簡潔了,程序的可讀性也提升了。可是Hibernate 在業務複雜的項目中使用也存在一些問題:
「半自動化」的ORM 框架MyBatis 就解決了這幾個問題。「半自動化」是相對於Hibernate 的全自動化來講的,也就是說它的封裝程度沒有Hibernate 那麼高,不會自動生成所有的SQL 語句,主要解決的是SQL 和對象的映射問題。在MyBatis 裏面,SQL 和代碼是分離的,因此會寫SQL 基本上就會用MyBatis,沒有額外的學習成本。咱們來總結一下,MyBatis 的核心特性,或者說它解決的主要問題是什麼:
固然,須要明白的是,Hibernate 和MyBatis 跟DbUtils、Spring JDBC 同樣,都是對JDBC 的一個封裝,咱們去看源碼,最後必定會看到Statement 和ResultSet 這些對象。
問題來了,咱們有這麼多的工具和不一樣的框架,在實際的項目裏面應該怎麼選擇?在一些業務比較簡單的項目中,咱們可使用Hibernate;若是須要更加靈活的SQL,可使用MyBatis,對於底層的編碼,或者性能要求很是高的場合,能夠用JDBC。實際上在咱們的項目中,MyBatis 和Spring JDBC 是能夠混合使用的。固然,咱們也根據項目的需求本身寫ORM 框架。下文咱們將慢慢的對Mybatis進行更深刻的學習。
參考: