The development history of ORM framework


image.png

JDBC

Characteristics of JDBC operations

At first, the database was directly operated through jdbc. If the local database has a t_user table, the operation process is

// 注册 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);
}

The specific steps are to first introduce the MySQL driver dependency in pom.xml and pay attention to the version of the MySQL database.

  1. Class.forName registration driver
  2. Get a Connection object
  3. Create a Statement object
  4. The execute() method executes the SQL statement and obtains the ResultSet result set
  5. Assign values ​​to POJO properties through the ResultSet result set
  6. Finally close related resources

The first impression this implementation gives us is that the operation steps are relatively cumbersome, which will be even more troublesome in complex business scenarios. In particular, you need to maintain the connection to the management resource yourself. If you forget it, it is likely to cause the database service connection to be exhausted. At the same time, specific business SQL statements are directly written in the code to enhance coupling. Each connection will go through these steps, with a lot of repeated code. To summarize the characteristics of the above operations:

  1. code duplication
  2. Resource management
  3. Result set processing
  4. SQL coupling

JDBC Optimization 1.0

In view of the characteristics of regular jdbc operations, we can first optimize from the aspects of code duplication and resource management. We can create a tool class to specifically deal with this problem.

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();
            }
        }
    }
}

The corresponding jdbc operation code can be simplified as follows

    /**
     *
     * 通过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);
        }
    }

However, the overall operation steps will still appear more complicated.

JDBC Optimization 2.0

To optimize the DML operation method, first solve the problem of SQL coupling and encapsulate the DML operation method in 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;
    }

Then during DML operation we can simplify it to the following steps

    /**
     * 通过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();
        }
    }

Obviously this method will be much simpler than the initial use, but it still does not solve the problem of ResultSet result set processing during query processing, so it needs to continue to be optimized.

JDBC Optimization 3.0

Optimization of ResultSet needs to start from two aspects: reflection and metadata, as follows:

    /**
     * 查询方法的简易封装
     * @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);
            }
        }
    }

After encapsulating the above method, our query operation can be simplified to

    /**
     *
     * 通过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();
        }
    }

In this way, you only need to focus on core SQL operations when operating data in the database. Of course, the above design is still relatively rough. At this time, DbUtils under Apache is a good choice.

Apache DBUtils

Initial configuration

DButils provides a QueryRunner class, which encapsulates the addition, deletion, modification, and query methods of the database, and how to obtain the 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);
    }

When creating a QueryRunner object, we need to pass a DataSource object. At this time, we can choose common connection pool tools such as Druid or Hikai. Druid is used here.

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

Basic operations

The methods provided in QueryRunner solve the problem of duplicate code, and the incoming data source solves the problem of resource management. The processing of ResultSet result set is handled by ResultSetHandler. We can implement this interface ourselves

    /**
     * 查询所有的用户信息
     * @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);
        }
    }

Or use the default related implementation provided in DBUtils to solve the problem

    /**
     * 通过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);
        }
    }

DBUtils encapsulated by Apache can easily help us implement relatively simple database operations.

SpringJDBC

Under the Spring framework platform, JDBC encapsulation operations are also provided. Spring provides a template method JdbcTemplate, which encapsulates various execute, query and update methods.

The JdbcTemplate class is the central class of the JDBC core package. It simplifies the operation of JDBC and avoids common exceptions. It encapsulates the core process of JDBC. The application only needs to provide SQL statements and extract the result set. It is thread-safe. .

Initial configuration

When using SpringJdbcTemplate, we still need to configure the corresponding data source, and then inject the JdbcTemplate object into the IoC container.

@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 operations

When operating data in the database, you only need to obtain the JdbcTemplate instance from the container.

@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);
        }
    }

}

Hibernate

Although Apache DBUtils and SpringJdbcTemplate simplify database operations, the functions they provide are relatively simple (lack of caching, transaction management, etc.), so in actual development, the above technologies are often not used directly, but Hibernate and MyBatis are used. These professional ORM persistence layer frameworks.

ORM introduction

ORM (Object Relational Mapping), which is the mapping of objects and relationships. The object is the object in the program, and the relationship is its relationship with the data in the database. In other words, the problem that the ORM framework helps us solve is program objects and relationships. Database mapping issues

Use of Hibernate

Hibernate is a very popular ORM framework. The first version was released in 2001. The usage steps are as follows

Create project

Just create a Maven project and add the relevant dependencies. We will handle it directly through the dependencies of SpringDataJpa here.

<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>

Configuration file

When using Hibernate, we need to create some hbm xml mapping files for entity classes

<?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>

And Hibernate configuration file

<?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 operations

Then in the program we can implement CRUD operations through the Session object provided by Hibernate

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();
    }
}

other methods

At the location of the mapping file, we can also replace the mapping file by annotating it.

@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;
}

The JPA provided to us in Spring uniformly encapsulates the persistence layer framework, and is essentially implemented based on HibernateJPA, so we can also operate it through the API of SpringDataJPA when using it.

The dao interface only needs to inherit the JpaRepository interface.

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

Normal processing of service layer

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);
    }
}

HibernateSummary

The emergence of Hibernate has greatly simplified our database operations and can also better cope with more complex business scenarios. Hibernate has the following characteristics

  1. Customized SQL generation based on database dialect, good portability
  2. Automatically manage connection resources
  3. Complete mapping of objects and relational data is achieved, and operating objects is just like operating database records.
  4. Provides a caching mechanism

Hibernate also has some problems when dealing with complex businesses.

  1. For example, the get(), update() and save() methods in the API actually operate on all fields, and there is no way to specify some fields. In other words, they are not flexible enough.
  2. Customizing the way to generate SQL is also very difficult if you want to do some optimization based on SQL.
  3. Dynamic SQL is not supported, such as table names, conditions, parameter changes in sub-tables, etc. SQL cannot be automatically generated based on conditions.

Therefore we need a more flexible framework

MyBatis

MyBatis is an excellent persistence layer framework that supports custom SQL, stored procedures, and advanced mapping. MyBatis eliminates almost all JDBC code and the work of setting parameters and getting result sets. MyBatis can configure and map primitive types, interfaces and Java POJOs (Plain Old Java Objects) into records in the database through simple XML or annotations.

The "semi-automatic" ORM framework can well solve the several problems of Hibernate mentioned above. "Semi-automatic" is compared to the full automation of Hibernate. Its degree of encapsulation is not as high as that of Hibernate, and it will not automatically generate all SQL statements mainly solve the mapping problem between SQL and objects.

MyBatis, formerly ibatis, was developed in 2001 and is a combination of the words "internet" and "abatis ['æbətɪs] (obstacle)". Donated to Apache in 2004. In 2010, it was renamed MyBatis.

In MyBatis, SQL and code are separated, so if you can write SQL, you will basically use MyBatis, and there is no additional learning cost.

Guess you like

Origin blog.csdn.net/qq_28314431/article/details/133083035