在工程开发中,大概率会使用到关系型数据库,一般在最常用的就是Mysql了。当在Java工程中使用Mysql时,我们需要获取与Mysql的连接,然后才能使用一些方法,操作Mysql。在使用Java操作Mysql的问题上,我们最开始学习时,一般是通过JDBC实现的。然后我们一般会接触到一些ORM框架,比如Mybatis、Spring Data Jpa(Hibernate)。在国内,Mybatis应用更多,生态也更加完善。Mybatis虽然使用起来比较简单,但是还是有很多细节值得探讨,这对我们更好地了解ORM框架有很大的帮助。所以本篇文章开始,会探索ORM框架Mybatis的相关实现细节,这也是我们学习最常用开发框架SSM的最后一个部分。
本篇文章,我们先来看一些基础问题,比如Java怎么操作Mysql,为什么要使用ORM框架,Mybatis能给我们带来什么好处等。
1. JDBC
提到Java操作Mysql,不可避免的要提到JDBC。JDBC(JavaDataBase Connectivity)就是Java数据库连接,说白了就是用Java语言来操作数据库。
早期SUN公司编写一套可以连接所有数据库的API,但是发现这是一件很困难的事情,因为数据库种类繁多,并且各个厂商的数据库服务器差异也很大。后来SUN开始与数据库厂商们讨论,由SUN提供一套访问数据库的规范(一组接口),并提供连接数据库的协议标准,然后各个数据库厂商会遵循SUN的规范提供一套访问自己公司的数据库服务器的API。SUN提供的规范命名为JDBC,而各个厂商提供的遵循了JDBC规范,可以访问自己数据库的API被称之为驱动。
下面以Java连接Mysql为例,来看一下如何使用JDBC来连接和操作数据库的。
1.1 新建user_info表
# 新建数据库zhuoli_db
create database zhuoli_db;
# 新建user_info表
CREATE TABLE user_info (
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
name varchar(32) NOT NULL COMMENT '名称',
age int(3) NOT NULL COMMENT '年龄',
PRIMARY KEY (id)
);
1.2 创建实体类
为表user_info创建对应的实体类UserInfo。
public class UserInfo {
private Long id;
private String name;
private Integer age;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "UserInfo{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
1.3 创建JDBC数据库连接
public class DbUtil {
private static final String URL = "jdbc:mysql://localhost:3306/zhuoli_db";
private static final String USER = "zhuoli";
private static final String PASSWORD = "zhuoli";
private static Connection conn = null;
static{
try {
//1.加载驱动程序
Class.forName("com.mysql.cj.jdbc.Driver");
//2. 获得数据库连接
conn = DriverManager.getConnection(URL, USER, PASSWORD);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
}
public static Connection getConnection(){
return conn;
}
}
1.4 通过JDBC操作数据库
public class TestDao {
//增加
public void addUser(UserInfo userInfo) throws SQLException {
//获取连接
Connection conn = DbUtil.getConnection();
//sql
String sql = "INSERT INTO user_info(name, age)" + "values(" + "?, ?)";
//预编译sql
PreparedStatement ptmt = conn.prepareStatement(sql); //预编译SQL,减少sql执行
//传参
ptmt.setString(1, userInfo.getName());
ptmt.setInt(2, userInfo.getAge());
//执行
ptmt.execute();
}
public void updateUserAge(Long id, Integer age) throws SQLException {
//获取连接
Connection conn = DbUtil.getConnection();
//sql, 每行加空格
String sql = "UPDATE user_info set age = ? " + " where id = ?";
//预编译sql
PreparedStatement ptmt = conn.prepareStatement(sql);
//传参
ptmt.setInt(1, age);
ptmt.setLong(2, id);
//执行
ptmt.execute();
}
public void delUser(Long id) throws SQLException {
//获取连接
Connection conn = DbUtil.getConnection();
//sql
String sql = "delete from user_info where id= ?";
//预编译SQL
PreparedStatement ptmt = conn.prepareStatement(sql);
//传参
ptmt.setLong(1, id);
//执行
ptmt.execute();
}
public List<UserInfo> queryAllUser() throws SQLException {
Connection conn = DbUtil.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM user_info");
List<UserInfo> userInfoList = new LinkedList<>();
while (rs.next()) {
UserInfo userInfo = new UserInfo();
userInfo.setId(rs.getLong("id"));
userInfo.setName(rs.getString("name"));
userInfo.setAge(rs.getInt("age"));
userInfoList.add(userInfo);
}
return userInfoList;
}
public UserInfo queryUserInfoById(Long id) throws SQLException {
UserInfo userInfo = null;
//获取连接
Connection conn = DbUtil.getConnection();
//sql
String sql = "select * from user_info where id = ?";
//预编译SQL
PreparedStatement ptmt = conn.prepareStatement(sql);
//传参
ptmt.setLong(1, id);
//执行
ResultSet rs = ptmt.executeQuery();
while (rs.next()) {
userInfo = new UserInfo();
userInfo.setId(rs.getLong("id"));
userInfo.setName(rs.getString("name"));
userInfo.setAge(rs.getInt("age"));
}
return userInfo;
}
}
综合1.2和1.3节,我们可以知道,JDBC操作数据库的步骤大致如下:
- 加载驱动程序
- 获得数据库连接
- 创建Statement
- 执行Statement
1.5 测试代码
public class JdbcTest {
public static void main(String[] args) throws Exception {
TestDao testDao = new TestDao();
System.out.println("query all");
testDao.queryAllUser().forEach(System.out::println);
System.out.println("------------------------------");
UserInfo addUser = new UserInfo();
addUser.setName("Michael");
addUser.setAge(18);
testDao.addUser(addUser);
System.out.println("add new User name = Michael, age = 20, after execute add: ");
testDao.queryAllUser().forEach(System.out::println);
System.out.println("------------------------------");
testDao.updateUserAge(1L, 20);
System.out.println("update userId1's age to 20, after execute update: ");
testDao.queryAllUser().forEach(System.out::println);
System.out.println("------------------------------");
System.out.println("queryById, id = 1, result is:");
System.out.println(testDao.queryUserInfoById(1L));
System.out.println("------------------------------");
}
}
也就是说,我们成功通过JDBC连接并对数据库进行了增删改查。
但是,上述代码中,不难发现,其实有一些不方便的地方。
- 比如sql传参和对象映射很不方便,我们需要在代码中需要多次手动去写这部分“重复”的逻辑,
- 每次执行sql前都要去获取连接,创建Statement
有没有什么方法,能让我们访问数据库更加方便简洁?答案就是我们接下来要介绍的ORM框架,这里我们使用Mybatis。
2. Mybatis
mybatis是一个持久层ORM框架。它内部封装了jdbc,可以通过xml或注解完成ORM映射关系配置。使开发更简洁,更高效。使用Mybatis,可以让开发者只需要关注sql语句本身,简化JDBC操作,不需要在关注加载驱动、创建连接、处理SQL语句等繁杂的过程。下面来看一下通过Mybatis又是如何连接并操作数据库的。
2.1 创建实体类
数据库和表,我们还是用上面介绍JDBC时,使用的zhuoli_db.user_info。所以第一步,我们还是创建一个实体类Model对象:
public class UserInfo {
private Long id;
private String name;
private Integer age;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "UserInfo{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
Model实体对象,跟上述JDBC时创建的实体对象完全一致。
2.2 创建Mapper接口
public interface UserInfoMapper {
void addUser(UserInfo userInfo);
void updateUserAge(@Param("id") Long id, @Param("age") Integer age);
void delUserById(Long id);
List<UserInfo> queryAllUser();
UserInfo queryUserInfoById(Long id);
}
2.3 定义mapper接口对应的mapper文件user_info.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.zhuoli.service.mybatis.explore.mapper.UserInfoMapper">
<resultMap id="userInfoResult" type="com.zhuoli.service.mybatis.explore.model.UserInfo">
<result property="id" column="id" jdbcType="BIGINT"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="age" column="age" jdbcType="INTEGER"/>
</resultMap>
<select id="queryAllUser" resultMap="userInfoResult">
select * from user_info;
</select>
<select id="queryUserInfoById" resultMap="userInfoResult" parameterType="java.lang.Long">
select * from user_info where id = #{id};
</select>
<insert id="addUser" parameterType="com.zhuoli.service.mybatis.explore.model.UserInfo" useGeneratedKeys="true" keyProperty="id">
insert into user_info (name, age) values (#{name}, #{age})
</insert>
<delete id="delUserById" parameterType="java.lang.Long">
delete from user_info where id = #{id};
</delete>
<update id="updateUserAge">
update user_info set age = #{age} where id = #{id}
</update>
</mapper>
其实就是上述mapper接口的sql实现,至于为什么没有Java实现,就可以通过Mapper接口操作Mysql,后面我们再介绍。
2.4 创建Mybatis配置文件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>
<!-- 引入外部配置文件 -->
<properties resource="dataSource.properties"></properties>
<environments default="default">
<environment id="default">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${dataSource.driver}"/>
<property name="url" value="${dataSource.url}"/>
<property name="username" value="${dataSource.username}"/>
<property name="password" value="${dataSource.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/user_info.xml"/>
</mappers>
</configuration>
这里数据源配置使用了占位符,跟我们之前Spring的占位符有点像,但这里其实跟Spring没有关系,其实是Mybatis通过引入外部配置文件中的属性实现的,即dataSource.properties文件中的属性,如下:
dataSource.driver=com.mysql.cj.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/zhuoli_db
dataSource.username=zhuoli
dataSource.password=zhuoli
2.5 测试代码
public class Test1 {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.class);
System.out.println("query all user");
List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
userInfoList.forEach(System.out::println);
System.out.println("---------------------");
System.out.println("queryUserInfoById id = 1");
System.out.println(userInfoMapper.queryUserInfoById(1L));
System.out.println("---------------------");
System.out.println("addUser");
UserInfo addUser = new UserInfo();
addUser.setAge(14);
addUser.setName("paul");
userInfoMapper.addUser(addUser);
userInfoMapper.queryAllUser().forEach(System.out::println);
System.out.println("---------------------");
System.out.println("update User 1L age to 22");
userInfoMapper.updateUserAge(1L, 22);
userInfoMapper.queryAllUser().forEach(System.out::println);
System.out.println("---------------------");
System.out.println("delete user 1L");
userInfoMapper.delUserById(1L);
userInfoMapper.queryAllUser().forEach(System.out::println);
} finally {
sqlSession.close();
}
}
}
运行结果:
说明通过Mybatis,我们也成功完成了对数据库的增删改查。不难发现,使用Mybatis后,简化了JDBC操作,我们可以更关注SQL本身,而不用重复去做一些连接获取、对象映射的操作。
参考链接:
1. Mybatis是什么以及Mybatis和JDBC的关系?
2. 什么是ORM
3. JDBC 使用说明