本文已参与「新人创作礼」活动,一起开启掘金创作之路。
引言
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和表做一个关联对应。如下图:
比如: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官网是这么介绍他们的产品的:
- MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- 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>
项目结构如下:
创建一个 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}。整个一个过程大致如下:
还有一种方法:
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 作用域或者方法体作用域。