常見ORM框架及JDBC操做工具類

  在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

  • 第一步,註冊驅動,第二步,經過DriverManager 獲取一個Connection,參數裏面填數據庫地址,用戶名和密碼。
  • 第三步,咱們經過Connection 建立一個Statement 對象。
  • 第四步,經過Statement 的execute()方法執行SQL。固然Statement 上面定義了很是多的方法。execute()方法返回一個ResultSet 對象,咱們把它叫作結果集。
  • 第五步,咱們經過ResultSet 獲取數據。轉換成一個POJO 對象。
  • 最後,咱們要關閉數據庫相關的資源,包括ResultSet、Statement、Connection,它們的關閉順序和打開的順序正好是相反的。這個就是咱們經過JDBC 的API 去操做數據庫的方法,這個僅僅是一個查詢。若是咱們項目當中的業務比較複雜,表很是多,各類操做數據庫的增刪改查的方法也比較多的話,那麼這樣代碼會重複出現不少次。

  在每一段這樣的代碼裏面,咱們都須要本身去管理數據庫的鏈接資源,若是忘記寫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

Apache DbUtils:

  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 要求數據庫的字段跟對象的屬性名稱徹底一致,才能夠實現自動映射。

Spring JDBC:

  除了DbUtils 以外,Spring 也對原生的JDBC 進行了封裝,而且給咱們提供了一個模板方法JdbcTemplate,來簡化咱們對數據庫的操做。

  •   第一個,咱們再也不須要去關心資源管理的問題。
  •   第二個,對於結果集的處理,Spring JDBC 也提供了一個RowMapper 接口,能夠把結果集轉換成Java 對象。

  看代碼:好比咱們要把結果集轉換成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 作了輕量級封裝的框架,或者說工具類裏面,都幫助咱們解決了一些問題:

  1. 不管是QueryRunner 仍是JdbcTemplate,均可以傳入一個數據源進行初始化,也就是資源管理這一部分的事情,能夠交給專門的數據源組件去作,不用咱們手動建立和關閉;
  2. 對操做數據的增刪改查的方法進行了封裝;
  3. 能夠幫助咱們映射結果集,不管是映射成List、Map 仍是實體類。

  可是仍是存在一些缺點:

  1. SQL 語句都是寫死在代碼裏面的,依舊存在硬編碼的問題;
  2. 參數只能按固定位置的順序傳入(數組),它是經過佔位符去替換的,不能自動映射;
  3. 在方法裏面,能夠把結果集映射成實體類,可是不能直接把實體類映射成數據庫的記錄(沒有自動生成SQL 的功能);
  4. 查詢沒有緩存的功能。

Hibernate:

  要解決這些問題,使用這些工具類仍是不夠的,要用到咱們今天講的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 在業務複雜的項目中使用也存在一些問題:

  1. 好比使用get()、save() 、update()對象的這種方式,實際操做的是全部字段,沒有辦法指定部分字段,換句話說就是不夠靈活。
  2. 這種自動生成SQL 的方式,若是咱們要去作一些優化的話,是很是困難的,也就是說可能會出現性能比較差的問題。
  3. 不支持動態SQL(好比分表中的表名變化,以及條件、參數)。

MyBatis:

  「半自動化」的ORM 框架MyBatis 就解決了這幾個問題。「半自動化」是相對於Hibernate 的全自動化來講的,也就是說它的封裝程度沒有Hibernate 那麼高,不會自動生成所有的SQL 語句,主要解決的是SQL 和對象的映射問題。在MyBatis 裏面,SQL 和代碼是分離的,因此會寫SQL 基本上就會用MyBatis,沒有額外的學習成本。咱們來總結一下,MyBatis 的核心特性,或者說它解決的主要問題是什麼:

  1. 使用鏈接池對鏈接進行管理
  2. SQL 和代碼分離,集中管理
  3. 結果集映射
  4. 參數映射和動態SQL
  5. 重複SQL 的提取
  6. 緩存管理
  7. 插件機制

  固然,須要明白的是,Hibernate 和MyBatis 跟DbUtils、Spring JDBC 同樣,都是對JDBC 的一個封裝,咱們去看源碼,最後必定會看到Statement 和ResultSet 這些對象。

問題來了,咱們有這麼多的工具和不一樣的框架,在實際的項目裏面應該怎麼選擇?在一些業務比較簡單的項目中,咱們可使用Hibernate;若是須要更加靈活的SQL,可使用MyBatis,對於底層的編碼,或者性能要求很是高的場合,能夠用JDBC。實際上在咱們的項目中,MyBatis 和Spring JDBC 是能夠混合使用的。固然,咱們也根據項目的需求本身寫ORM 框架。下文咱們將慢慢的對Mybatis進行更深刻的學習。

參考:

  1. 業務簡單的項目可使用Hibernate
  2. 須要靈活的SQL,能夠用MyBatis
  3. 對性能要求高,可使用JDBC
  4. Spring JDBC能夠和ORM框架混用
相關文章
相關標籤/搜索