ORMフレームワークの開発経緯


画像.png

JDBC

JDBC操作の特徴

最初はjdbc経由でデータベースを直接操作していましたが、ローカルデータベースにt_userテーブルがある場合の操作プロセスは

// 注册 JDBC 驱动
Class.forName("com.mysql.cj.jdbc.Driver");

// 打开连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db?characterEncoding=utf-8&serverTimezone=UTC", "root", "123456");

// 执行查询
stmt = conn.createStatement();
String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = 1";
ResultSet rs = stmt.executeQuery(sql);

// 获取结果集
while (rs.next()) {
    
    
    Integer id = rs.getInt("id");
    String userName = rs.getString("user_name");
    String realName = rs.getString("real_name");
    String password = rs.getString("password");
    Integer did = rs.getInt("d_id");
    user.setId(id);
    user.setUserName(userName);
    user.setRealName(realName);
    user.setPassword(password);
    user.setDId(did);

    System.out.println(user);
}

具体的な手順では、まず、pom.xml に MySQL ドライバーの依存関係を導入し、MySQL データベースのバージョンに注意します。

  1. Class.forName 登録ドライバー
  2. 接続オブジェクトを取得する
  3. Statement オブジェクトを作成する
  4. execute() メソッドは SQL ステートメントを実行し、ResultSet 結果セットを取得します。
  5. ResultSet 結果セットを通じて POJO プロパティに値を割り当てる
  6. 最後に関連リソースを閉じます

この実装の第一印象は、操作手順が比較的面倒であるということですが、複雑なビジネス シナリオではさらに面倒になるでしょう。特に管理リソースへの接続はお客様自身で維持する必要があり、これを忘れるとデータベースサービスの接続が枯渇する可能性があります。同時に、結合を強化するために、特定のビジネス SQL ステートメントがコードに直接書き込まれます。各接続では、多くのコードが繰り返され、これらの手順が実行されます。上記の操作の特徴を要約すると、次のようになります。

  1. コードの重複
  2. 資源管理
  3. 結果セットの処理
  4. SQL結合

JDBC最適化1.0

通常の jdbc 操作の特性を考慮して、まずコードの重複とリソース管理の側面から最適化することができ、この問題に特化したツール クラスを作成できます。

public class DBUtils {
    
    

    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/db?characterEncoding=utf-8&serverTimezone=UTC";
    private static final String JDBC_NAME = "root";
    private static final String JDBC_PASSWORD = "123456";

    private static  Connection conn;

    /**
     * 对外提供获取数据库连接的方法
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
    
    
        if(conn == null){
    
    
            try{
    
    
                conn = DriverManager.getConnection(JDBC_URL,JDBC_NAME,JDBC_PASSWORD);
            }catch (Exception e){
    
    
                e.printStackTrace();
                throw new Exception();
            }
        }
        return conn;
    }

    /**
     * 关闭资源
     * @param conn
     */
    public static void close(Connection conn ){
    
    
        close(conn,null);
    }

    public static void close(Connection conn, Statement sts ){
    
    
        close(conn,sts,null);
    }

    public static void close(Connection conn, Statement sts , ResultSet rs){
    
    
        if(rs != null){
    
    
            try {
    
    
                rs.close();
            }catch (Exception e){
    
    
                e.printStackTrace();
            }
        }

        if(sts != null){
    
    
            try {
    
    
                sts.close();
            }catch (Exception e){
    
    
                e.printStackTrace();
            }
        }

        if(conn != null){
    
    
            try {
    
    
                conn.close();
            }catch (Exception e){
    
    
                e.printStackTrace();
            }
        }
    }
}

対応する jdbc オペレーション コードは次のように簡略化できます。

    /**
     *
     * 通过JDBC查询用户信息
     */
    public void queryUser(){
    
    
        Connection conn = null;
        Statement stmt = null;
        User user = new User();
        ResultSet rs = null;
        try {
    
    
            // 注册 JDBC 驱动
            // Class.forName("com.mysql.cj.jdbc.Driver");

            // 打开连接
            conn = DBUtils.getConnection();
            // 执行查询
            stmt = conn.createStatement();
            String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = 1";
            rs = stmt.executeQuery(sql);

            // 获取结果集
            while (rs.next()) {
    
    
                Integer id = rs.getInt("id");
                String userName = rs.getString("user_name");
                String realName = rs.getString("real_name");
                String password = rs.getString("password");
                Integer did = rs.getInt("d_id");
                user.setId(id);
                user.setUserName(userName);
                user.setRealName(realName);
                user.setPassword(password);
                user.setDId(did);
                System.out.println(user);
            }

        } catch (SQLException se) {
    
    
            se.printStackTrace();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            DBUtils.close(conn,stmt,rs);
        }
    }

   /**
     * 通过JDBC实现添加用户信息的操作
     */
    public void addUser(){
    
    
        Connection conn = null;
        Statement stmt = null;
        try {
    
    
            // 打开连接
            conn = DBUtils.getConnection();
            // 执行查询
            stmt = conn.createStatement();
            String sql = "INSERT INTO T_USER(user_name,real_name,password,age,d_id)values('wangwu','王五','111',22,1001)";
            int i = stmt.executeUpdate(sql);
            System.out.println("影响的行数:" + i);
        } catch (SQLException se) {
    
    
            se.printStackTrace();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            DBUtils.close(conn,stmt);
        }
    }

ただし、全体的な操作手順はさらに複雑になります。

JDBC最適化2.0

DML 操作メソッドを最適化するには、まず SQL 結合の問題を解決し、DML 操作メソッドを DBUtils にカプセル化します。

    /**
     * 执行数据库的DML操作
     * @return
     */
    public static Integer update(String sql,Object ... paramter) throws Exception{
    
    
        conn = getConnection();
        PreparedStatement ps = conn.prepareStatement(sql);
        if(paramter != null && paramter.length > 0){
    
    
            for (int i = 0; i < paramter.length; i++) {
    
    
                ps.setObject(i+1,paramter[i]);
            }
        }
        int i = ps.executeUpdate();
        close(conn,ps);
        return i;
    }

その後、DML 操作中に次の手順に簡略化できます。

    /**
     * 通过JDBC实现添加用户信息的操作
     */
    public void addUser(){
    
    
        String sql = "INSERT INTO T_USER(user_name,real_name,password,age,d_id)values(?,?,?,?,?)";
        try {
    
    
            DBUtils.update(sql,"wangwu","王五","111",22,1001);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

明らかに、この方法は最初に使用したときよりもはるかに単純になりますが、クエリ処理中の ResultSet 結果セット処理の問題はまだ解決されていないため、引き続き最適化する必要があります。

JDBC最適化3.0

ResultSet の最適化は、次のように、リフレクションとメタデータの 2 つの側面から開始する必要があります。

    /**
     * 查询方法的简易封装
     * @param sql
     * @param clazz
     * @param parameter
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> List<T> query(String sql, Class clazz, Object ... parameter) throws  Exception{
    
    
        conn = getConnection();
        PreparedStatement ps = conn.prepareStatement(sql);
        if(parameter != null && parameter.length > 0){
    
    
            for (int i = 0; i < parameter.length; i++) {
    
    
                ps.setObject(i+1,parameter[i]);
            }
        }
        ResultSet rs = ps.executeQuery();
        // 获取对应的表结构的元数据
        ResultSetMetaData metaData = ps.getMetaData();
        List<T> list = new ArrayList<>();
        while(rs.next()){
    
    
            // 根据 字段名称获取对应的值 然后将数据要封装到对应的对象中
            int columnCount = metaData.getColumnCount();
            Object o = clazz.newInstance();
            for (int i = 1; i < columnCount+1; i++) {
    
    
                // 根据每列的名称获取对应的值
                String columnName = metaData.getColumnName(i);
                Object columnValue = rs.getObject(columnName);
                setFieldValueForColumn(o,columnName,columnValue);
            }
            list.add((T) o);
        }
        return list;
    }

    /**
     * 根据字段名称设置 对象的属性
     * @param o
     * @param columnName
     */
    private static void setFieldValueForColumn(Object o, String columnName,Object columnValue) {
    
    
        Class<?> clazz = o.getClass();
        try {
    
    
            // 根据字段获取属性
            Field field = clazz.getDeclaredField(columnName);
            // 私有属性放开权限
            field.setAccessible(true);
            field.set(o,columnValue);
            field.setAccessible(false);
        }catch (Exception e){
    
    
            // 说明不存在 那就将 _ 转换为 驼峰命名法
            if(columnName.contains("_")){
    
    
                Pattern linePattern = Pattern.compile("_(\\w)");
                columnName = columnName.toLowerCase();
                Matcher matcher = linePattern.matcher(columnName);
                StringBuffer sb = new StringBuffer();
                while (matcher.find()) {
    
    
                    matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
                }
                matcher.appendTail(sb);
                // 再次调用复制操作
                setFieldValueForColumn(o,sb.toString(),columnValue);
            }
        }
    }

上記のメソッドをカプセル化した後、クエリ操作は次のように単純化できます。

    /**
     *
     * 通过JDBC查询用户信息
     */
    public void queryUser(){
    
    
        try {
    
    
            String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = ?";
            List<User> list = DBUtils.query(sql, User.class,2);
            System.out.println(list);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

この方法では、データベース内のデータを操作するときに、コアとなる SQL 操作だけに集中する必要があります。もちろん、上記の設計はまだ比較的大まかなものですが、現時点では、Apache の DbUtils が適切な選択です。

Apache DBUtils

初期設定

DButils は、データベースの追加、削除、変更、クエリ メソッドと QueryRunner の取得方法をカプセル化する QueryRunner クラスを提供します。

    private static final String PROPERTY_PATH = "druid.properties";

    private static DruidDataSource dataSource;
    private static QueryRunner queryRunner;

    public static void init() {
    
    
        Properties properties = new Properties();
        InputStream in = DBUtils.class.getClassLoader().getResourceAsStream(PROPERTY_PATH);
        try {
    
    
            properties.load(in);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        dataSource = new DruidDataSource();
        dataSource.configFromPropety(properties);
        // 使用数据源初始化 QueryRunner
        queryRunner = new QueryRunner(dataSource);
    }

QueryRunner オブジェクトを作成するときは、DataSource オブジェクトを渡す必要がありますが、このとき、Druid や Hikai などの一般的な接続プール ツールを選択できます。ここでは Druid を使用します。

druid.username=root
druid.password=123456
druid.url=jdbc:mysql://localhost:3306/db?characterEncoding=utf-8&serverTimezone=UTC
druid.minIdle=10
druid.maxActive=30

基本操作

QueryRunner で提供されるメソッドは重複コードの問題を解決し、受信データ ソースはリソース管理の問題を解決します。ResultSet 結果セットの処理は、ResultSetHandler によって処理されます。このインターフェースは自分で実装できます

    /**
     * 查询所有的用户信息
     * @throws Exception
     */
    public void queryUser() throws Exception{
    
    
        DruidUtils.init();
        QueryRunner queryRunner = DruidUtils.getQueryRunner();
        String sql = "select * from t_user";
        List<User> list = queryRunner.query(sql, new ResultSetHandler<List<User>>() {
    
    
            @Override
            public List<User> handle(ResultSet rs) throws SQLException {
    
    
                List<User> list = new ArrayList<>();
                while(rs.next()){
    
    
                    User user = new User();
                    user.setId(rs.getInt("id"));
                    user.setUserName(rs.getString("user_name"));
                    user.setRealName(rs.getString("real_name"));
                    user.setPassword(rs.getString("password"));
                    list.add(user);
                }
                return list;
            }
        });
        for (User user : list) {
    
    
            System.out.println(user);
        }
    }

または、DBUtils で提供されるデフォルトの関連実装を使用して問題を解決します

    /**
     * 通过ResultHandle的实现类处理查询
     */
    public void queryUserUseBeanListHandle() throws Exception{
    
    
        DruidUtils.init();
        QueryRunner queryRunner = DruidUtils.getQueryRunner();
        String sql = "select * from t_user";
        // 不会自动帮助我们实现驼峰命名的转换
        List<User> list = queryRunner.query(sql, new BeanListHandler<User>(User.class));
        for (User user : list) {
    
    
            System.out.println(user);
        }
    }

Apache によってカプセル化された DBUtils は、比較的単純なデータベース操作の実装に簡単に役立ちます。

SpringJDBC

Spring フレームワーク プラットフォームでは、JDBC カプセル化操作も提供されており、Spring ではさまざまな実行、クエリ、更新メソッドをカプセル化するテンプレート メソッド JdbcTemplate が提供されています。

JdbcTemplate クラスは、JDBC コア パッケージの中心的なクラスです。JDBC の操作を簡素化し、一般的な例外を回避します。JDBC のコア プロセスをカプセル化します。アプリケーションは SQL ステートメントを提供し、結果セットを抽出するだけで済みます。これはスレッドです。安全です。

初期設定

SpringJdbcTemplate を使用する場合は、対応するデータ ソースを設定してから、JdbcTemplate オブジェクトを IoC コンテナに挿入する必要があります。

@Configuration
@ComponentScan
public class SpringConfig {
    
    

    @Bean
    public DataSource dataSource(){
    
    
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://localhost:3306/db?characterEncoding=utf-8&serverTimezone=UTC");
        return  dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
    
    
        JdbcTemplate template = new JdbcTemplate();
        template.setDataSource(dataSource);
        return template;
    }
}

CRUD操作

データベース内のデータを操作する場合、コンテナから JdbcTemplate インスタンスを取得するだけで済みます。

@Repository
public class UserDao {
    
    

    @Autowired
    private JdbcTemplate template;

    public void addUser(){
    
    
        int count = template.update("insert into t_user(user_name,real_name)values(?,?)","bobo","波波老师");
        System.out.println("count = " + count);
    }

    public void query2(){
    
    
        String sql = "select * from t_user";
        List<User> list = template.query(sql, new BeanPropertyRowMapper<>(User.class));
        for (User user : list) {
    
    
            System.out.println(user);
        }
    }

}

休止状態

Apache DBUtils や SpringJdbcTemplate はデータベース操作を簡略化しますが、提供する機能が比較的シンプルであるため(キャッシュやトランザクション管理がないなど)、実際の開発では上記の技術を直接使用することはなく、Hibernate や MyBatis が使用されることが多いです。プロフェッショナルな ORM 永続層フレームワーク。

ORMの紹介

ORM (オブジェクト リレーショナル マッピング) オブジェクトとリレーションシップのマッピングです。オブジェクトはプログラム内のオブジェクトであり、リレーションシップはデータベース内のデータとの関係です。言い換えれば、ORM フレームワークが役立つ問題プログラムのオブジェクトと関係を解決する データベースのマッピングの問題

休止状態の使用

Hibernate は非常に人気のある ORM フレームワークで、最初のバージョンは 2001 年にリリースされました。利用手順は以下の通りです

プロジェクトの作成

Maven プロジェクトを作成し、関連する依存関係を追加するだけで、ここでは SpringDataJpa の依存関係を通じて直接処理します。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.11</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

設定ファイル

Hibernate を使用する場合、エンティティ クラス用の hbm 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>
    <class name="com.model.User" table="t_user">
        <id name="id" />
        <property name="userName" column="user_name"></property>
        <property name="realName" column="real_name"></property>
    </class>
</hibernate-mapping>

そしてHibernate設定ファイル

<?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 name="hibernate.connection.driver_class">
            com.mysql.cj.jdbc.Driver
        </property>
        <property name="hibernate.connection.url">
            jdbc:mysql://localhost:3306/db?characterEncoding=utf8&serverTimezone=UTC
        </property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">123456</property>
        <property name="hibernate.dialect">
            org.hibernate.dialect.MySQLDialect
        </property>

        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <property name="hibernate.hbm2ddl.auto">update</property>

        <mapping resource="User.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

CRUD操作

次に、プログラム内で、Hibernate が提供する Session オブジェクトを通じて CRUD 操作を実装できます。

public class HibernateTest {
    
    

    /**
     * Hibernate操作案例演示
     * @param args
     */
    public static void main(String[] args) {
    
    
        Configuration configuration = new Configuration();
        // 默认使用hibernate.cfg.xml
        configuration.configure();
        // 创建Session工厂
        SessionFactory factory = configuration.buildSessionFactory();
        // 创建Session
        Session session = factory.openSession();
        // 获取事务对象
        Transaction transaction = session.getTransaction();
        // 开启事务
        transaction.begin();
        // 把对象添加到数据库中
        User user = new User();
        user.setId(666);
        user.setUserName("hibernate");
        user.setRealName("持久层框架");
        session.save(user);
        transaction.commit();
        session.close();
    }
}

他の方法

マッピング ファイルの場所で、マッピング ファイルに注釈を付けて置き換えることもできます。

@Data
@Entity
@Table(name = "t_user")
public class User {
    
    

    @Id
    @Column(name = "id")
    private Integer id;

    @Column(name = "user_name")
    private String userName;

    @Column(name = "real_name")
    private String realName;

    @Column(name = "password")
    private String password;

    @Column(name = "age")
    private Integer age;

    @Column(name = "i_id")
    private Integer dId;
}

Springで提供されているJPAは永続化層のフレームワークを均一にカプセル化しており、基本的にはHibernateJPAをベースに実装されているため、利用する際にはSpringDataJPAのAPIを通じて操作することも可能です。

daoインターフェースはJpaRepositoryインターフェースを継承するだけで済みます。

public interface IUserDao extends JpaRepository<User,Integer> {
}

サービス層の通常処理

import java.util.List;
@Service
public class UserServiceImpl implements IUserService {
    
    

    @Autowired
    private IUserDao dao;

    @Override
    public List<User> query() {
    
    
        return dao.findAll();
    }

    @Override
    public User save(User user) {
    
    
        return dao.save(user);
    }
}

休止状態の概要

Hibernate の登場により、データベース操作が大幅に簡素化され、より複雑なビジネス シナリオにもより適切に対応できるようになりました。Hibernate には次のような特徴があります。

  1. データベース言語に基づいたカスタマイズされた SQL 生成、優れた移植性
  2. 接続リソースを自動的に管理する
  3. オブジェクトとリレーショナル データの完全なマッピングが実現され、オブジェクトの操作はデータベース レコードの操作とまったく同じです。
  4. キャッシュメカニズムを提供します

Hibernate は、複雑なビジネスを扱う場合にもいくつかの問題を抱えています。

  1. たとえば、API の get()、update()、save() メソッドは実際にはすべてのフィールドに対して動作しますが、一部のフィールドを指定する方法がないため、柔軟性が十分ではありません。
  2. SQL に基づいて最適化を行う場合、SQL の生成方法をカスタマイズすることも非常に困難です。
  3. テーブル名、条件、サブテーブルのパラメータ変更などの動的 SQL はサポートされていません。条件に基づいて SQL を自動生成することはできません。

したがって、より柔軟なフレームワークが必要です

マイバティス

MyBatis は、カスタム SQL、ストアド プロシージャ、および高度なマッピングをサポートする優れた永続層フレームワークです。MyBatis は、ほぼすべての JDBC コードと、パラメータの設定と結果セットの取得の作業を排除します。MyBatis は、単純な XML または注釈を使用して、プリミティブ型、インターフェイス、および Java POJO (Plain Old Java Object) を構成し、データベース内のレコードにマッピングできます。

「半自動」ORM フレームワークは、上記の Hibernate のいくつかの問題を十分に解決できます。「半自動」は、Hibernate の完全な自動化と比較されます。そのカプセル化の程度は Hibernate ほど高くはなく、また、カプセル化の程度は高くありません。すべての SQL ステートメントを自動的に生成することで、主に SQL とオブジェクト間のマッピングの問題が解決されます。

MyBatis (旧 ibatis) は 2001 年に開発され、「インターネット」と「abatis ['æbətɪs] (障害物)」という単語を組み合わせたものです。2004 年に Apache に寄付されました。2010 年に MyBatis に名前が変更されました。

MyBatisではSQLとコードが分離されているので、SQLが書けるなら基本的にMyBatisを使うことになり、追加の学習コストはかかりません。

おすすめ

転載: blog.csdn.net/qq_28314431/article/details/133083035