JDBC原生API编码转ORM框架Mybatis

499 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

引言

Java编程中,我们在编写数据库相关的代码时,一般使用Mybatis框架。如果不用框架,Java提供了JDBC这样一套API来供开发者操作数据库。那么本期就来讲解JDBC到Mybatis的转换过程。先来看JDBC编码:

public class DataBaseUtil {    
public static final String URL = "jdbc:mysql://localhost:3306/mblog";    
public static final String USER = "root";    
public static final String PASSWORD = "123456";
    public static void main(String[] args) throws Exception {   
    Class.forName("com.mysql.jdbc.Driver");       
    //2.         
    Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);        
    //3.        
    Statement stmt = conn.createStatement();        
    //4.        
    ResultSet rs = stmt.executeQuery("SELECT id, name, age FROM m_user where id =1");        
    //如果有数据,rs.next()返回true        
    while(rs.next()){            
    System.out.println("name: "+rs.getString("name")+" 年龄:"+rs.getInt("age"));        
    }    
  }
}

上面代码简单的展示 JDBC 操作数据的一个过程(正常不会这样,连异常都没做,这里仅作演示)。我们大致可以将其分为六个步骤:加载驱动程序;获得数据库连接;创建一个Statement对象;操作数据库,实现增删改查;获取结果集;关闭资源。

从使用层面来说,采用原生态的 JDBC 在项目中使用起来成本还是很高的。如果我们的项目中的业务相对比较复杂,数据库表也相对较多,各种操作数据库的增删改查的方法也会随之多起来,那么这样的代码重复次数会非常之多。

简单来说,把原生JDBC编程缺点归为这几点: 创建数据库的连接存在大量的硬编码;执行 statement 时存在硬编码;频繁的开启和关闭数据库连接,会严重影响数据库的性能,浪费数据库的资源;存在大量的重复性编码。为了解决这些问题,就诞生了各种各样方案,也就是我们现在广泛使用的各种 ORM 框架。

ORM

ORM( Object Relational Mapping),叫作对象关系映射,用于实现面向对象编程语言里不同类型系统的数据之间的转换。 简单的说,ORM 是通过使用描述对象和数据库之间映射的元数据,将程序中的对象与关系数据库相互映射。ORM 提供了实现持久化层的另一种模式,它采用映射元数据来描述对象关系的映射,使得 ORM 中间件能在任何一个应用的业务逻辑层和数据库层之间充当桥梁。

简单理解就是把pojo和表做一个关联对应。如下图:

image.png

比如:Apache DbUtils、Spring JDBC、 Hibernate、Ibatis(Mybatis 的前生)、Spring Data Jpa 等等。目前最为流行的是 Mybatis 和 Spring Data Jpa。我们主要看Mybatis,毕竟最受欢迎。

ORM有自己的优势,比如:提高了开发效率。由于 ORM 可以自动对 Entity 对象与数据库中的 Table 进行字段与属性的映射,所以我们实际可能已经不需要一个专用的、庞大的数据访问层。其次就是ORM 提供了对数据库的映射,不用 sql 直接编码,能够像操作对象一样从数据库获取数据。当然缺点也有,比如:牺牲程序的执行效率;还会固化思维模式,降低开发的灵活性。从系统结构上来看,采用 ORM 的系统一般都是多层系统,系统的层次多了,效率自然就会降低。ORM 是一种完全的面向对象的做法,而面向对象的做法也会对性能产生一定的影响。不然C早就被抛弃了。

在我们开发系统时,一般都有性能问题。性能问题主要产生在算法不正确和与数据库不正确的使用上。ORM 所生成的代码一般不太可能写出很高效的算法,在数据库应用上更有可能会被误用,主要体现在对持久对象的提取和和数据的加工处理上,如果用上了 ORM,程序员很有可能将全部的数据提取到内存对象中,然后再进行过滤和加工处理,这样就容易产生性能问题。

在对对象做持久化时 ORM 一般会持久化所有的属性。有时,这是不希望的。但 ORM 是一种工具,工具确实能解决一些重复,简单的劳动。这是不可否认的。我们不能指望工具能一劳永逸的解决所有问题,有些问题还是需要特殊处理的,但需要特殊处理的部分对绝大多数的系统,应该是很少的。

MyBatis编码实例

Mybatis官网是这么介绍他们的产品的:

  1. MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
  2. MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  3. MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

Mybatis的优势也有很多:基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响。SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用;与 JDBC 相比,减少了 50% 以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接;很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis 都支持);能够与 Spring 很好的集成;提供映射标签,支持对象与数据库的 ORM 字段关系映射,提供对象关系映射标签,支持对象关系组件维护。

我们先创建一张数据表,比如就建一张 m_user 表(使用 MySQL 数据库)。

CREATE TABLE `m_user` (  
`id` int(11) NOT NULL AUTO_INCREMENT,  
`name` varchar(255) DEFAULT NULL,  
`age` int(11) DEFAULT NULL,  
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

建好以后,不要忘记在pom里面添加依赖:

<dependency>            
            <groupId>org.mybatis</groupId>           
            <artifactId>mybatis</artifactId>          
            <version>3.5.2</version>        
          </dependency>       
          <dependency>            
                <groupId>mysql</groupId>            
                <artifactId>mysql-connector-java</artifactId>           
                <version>8.0.16</version>            
                <scope>runtime</scope>       
        </dependency>
</dependencies>

项目结构如下:

image.png

创建一个 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>    <environments default="development">        <environment id="development">            <transactionManager type="JDBC"/>            <dataSource type="POOLED">                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>                <property name="url" value="jdbc:mysql://localhost:3306/mblog?useUnicode=true"/>                <property name="username" value="root"/>                <property name="password" value="123456"/>            </dataSource>        </environment>    </environments>    <mappers>        <mapper resource="mapper/UserMapper.xml"/>    </mappers></configuration>

接着就是实体类:

public class User {    
    private Integer id;    
    private String name;    
    private Integer age;    
    //set get    
    @Override    
    public String toString() {        
        return "User{" + 
                     "id=" + id + 
                     ", name='" + name + ''' + 
                     ", age=" + age + 
                     '}';    
     }
}

根据实体类,我们需要创建 UserMapper.java类:

import com.tian.mybatis.entity.User;
public interface UserMapper {    
    User selectUserById(Integer id);
}

当然,最重要的是xml配置,创建 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="com.tian.mybatis.mapper.UserMapper">    
    <select id="selectUserById" resultType="com.tian.mybatis.entity.User">        
    select * from m_user where id = #{id}    
    </select>
</mapper>

最后我们再创建一个测试类:

import com.tian.mybatis.entity.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MybatisApplication {
     public static void main(String[] args) {        
         String resource = "mybatis-config.xml";        
         InputStream inputStream = null;        
         SqlSession sqlSession =null;        
         try {            
             inputStream = Resources.getResourceAsStream(resource);            
             //工厂模式            
             SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);            //sql操作会话            
             sqlSession = sqlSessionFactory.openSession();            
             //获取数据并解析成User对象            
             User user = sqlSession.selectOne("com.tian.mybatis.mapper.UserMapper.selectUserById", 1);
             System.out.println(user);        
         } catch (Exception e) {            
             e.printStackTrace();        
         }finally {            
             try {                
                 inputStream.close();            
             } catch (IOException e) {                
                 e.printStackTrace();            
             }            
             sqlSession.close();        
         }    
     }
}

运行后输出结果:User{id=1, name='tian', age=22}。整个一个过程大致如下:

image.png

还有一种方法:

import com.tian.mybatis.entity.User;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class MybatisApplication {
	public static void main( String[] args )
	{
		String		resource	= "mybatis-config.xml";
		InputStream	inputStream	= null;
		try {
			inputStream = Resources.getResourceAsStream( resource );
		} catch ( IOException e ) {
			e.printStackTrace();
		}
		/* 解析xml文件 */
		XMLConfigBuilder parser = new XMLConfigBuilder( inputStream, null, null );
		/* 构建一个SqlSessionFactory工厂类 */
		SqlSessionFactory sqlSessionFactory = build( parser.parse() );
		/* 创建一个SqlSession */
		SqlSession sqlSession = sqlSessionFactory.openSession();
		/* 获取数据并解析成User对象 */
		User user = sqlSession.selectOne( "com.tian.mybatis.mapper.UserMapper.selectUserById", 1 );
		System.out.println( user );
	}


	public static SqlSessionFactory build( Configuration config )
	{
		return(new DefaultSqlSessionFactory( config ) );
	}
}

输出和上面一样。还可以直接使用 Java 代码而不用 mybatis-config.xml。

/* 创建数据源 */
DataSource dataSource = getDataSource();

TransactionFactory transactionFactory = new JdbcTransactionFactory();

Environment environment = new Environment( "development", transactionFactory, dataSource );

Configuration configuration = new Configuration( environment );
configuration.addMapper( BlogMapper.class );

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build( configuration );

从上面这个案例中我们大致能猜到,Mybatis 中最主要几个组件:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper。SqlSessionFactoryBuilder 这个类可以被初始化、使用和丢弃,如果你已经创建好了一个 SqlSessionFactory 后就不用再保留它。因此 ,SqlSessionFactoryBuilder 的最好作用域是方法体内,比如说定义一个方法变量。你可以重复使用 SqlSessionFactoryBuilder 生成多个 SqlSessionFactory 实例,但是最好不要强行保留,因为 XML 的解析资源要用来做其它更重要的事。

SqlSessionFactory 一旦创建,SqlSessionFactory 就会在整个应用过程中始终存在。所以没有理由去销毁和再创建它,一个应用运行中也不建议多次创建 SqlSessionFactory。如果真的那样做,会显得很拙劣。因此 SqlSessionFactory 最好的作用域是 Application。可以有多种方法实现。

最简单的方法是单例模式或者是静态单例模式。然而这既不是广泛赞成和好用的。反而,使用 Google Guice 或 Spring 来进行依赖反射会更好。这些框架允 许你生成管理器来管理 SqlSessionFactory 的单例生命周期。SqlSession 每个线程都有自己的 SqlSession 实例,SqlSession 实例是不能被共享,也是不是线程安全的。因此最好使用 Request 作用域或者方法体作用域。