本文目录
1. MyBatis简介
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
持久层:数据库访问层(Data Access Layer)又称为DAL层,有时候也称为是持久层,其功能主要是负责数据库的访问。
1.1 什么是ORM?
对象关系映射 (Object Relational Mapping,简称ORM),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。
1.2 半自动/全自动ORM框架
- 定义实体类
public class User {
@ColumnName("id")
private int id;
@ColumnName("name")
private String name;
@ColumnName("age")
private int age;
// getters and setters
}
- 定义DAO接口
public interface UserDao {
void insert(User user);
void update(User user);
void delete(int id);
User get(int id);
List<User> getAll();
}
- 实现DAO接口
public class UserDaoImpl implements UserDao {
private Connection connection;
public UserDaoImpl(Connection connection) {
this.connection = connection;
}
@Override
public void insert(User user) {
try {
PreparedStatement statement = connection.prepareStatement("INSERT INTO user (name, age) VALUES (?, ?)");
statement.setString(1, user.getName());
statement.setInt(2, user.getAge());
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void update(User user) {
try {
PreparedStatement statement = connection.prepareStatement("UPDATE user SET name = ?, age = ? WHERE id = ?");
statement.setString(1, user.getName());
statement.setInt(2, user.getAge());
statement.setInt(3, user.getId());
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void delete(int id) {
try {
PreparedStatement statement = connection.prepareStatement("DELETE FROM user WHERE id = ?");
statement.setInt(1, id);
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public User get(int id) {
User user = null;
try {
PreparedStatement statement = connection.prepareStatement("SELECT * FROM user WHERE id = ?");
statement.setInt(1, id);
ResultSet resultSet = statement.executeQuery();
if (resultSet.next()) {
user = new User();
user.setId(resultSet.getInt("id"));
user.setName(resultSet.getString("name"));
user.setAge(resultSet.getInt("age"));
}
} catch (SQLException e) {
e.printStackTrace();
}
return user;
}
@Override
public List<User> getAll() {
List<User> userList = new ArrayList<>();
try {
PreparedStatement statement = connection.prepareStatement("SELECT * FROM user");
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
User user = new User();
user.setId(resultSet.getInt("id"));
user.setName(resultSet.getString("name"));
user.setAge(resultSet.getInt("age"));
userList.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
}
return userList;
}
}
通过上述示例代码,我们可以实现一个简单的全自动ORM框架,将Java对象映射到数据库表中,实现对数据的增删改查操作。
在ORM框架的实现过程我们就已经将SQL语句嵌入进去,因此在使用如Hibernate的ORM框架则不需要编写SQL语句,大大降低了程序员学习成本和SQL语句维护成本。而对于使用如MyBatis的半自动ORM框架进行开发,则需要手写SQL语句。
1.3 为什么要学习MyBatis
1.3.1 在持久层框架前我们是如何对数据库进行操作的?
以下是一个简单的Java JDBC示例代码,用于连接到MySQL数据库并执行一个SELECT查询:
import java.sql.*;
public class JdbcExample {
public static void main(String[] args) {
// JDBC连接信息
String url = "jdbc:mysql://localhost:3306/mydb";
String username = "root";
String password = "mypassword";
// SQL查询语句
String sql = "SELECT * FROM users";
try {
// 连接数据库
Connection connection = DriverManager.getConnection(url, username, password);
// 执行查询语句
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
// 处理结果集
while (resultSet.next()) {
String name = resultSet.getString("name");
String email = resultSet.getString("email");
System.out.println("Name: " + name + ", Email: " + email);
}
// 关闭连接
resultSet.close();
statement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
在上面的代码中,我们首先定义了JDBC连接信息,包括URL、用户名和密码,然后定义了一个SELECT查询语句。接下来,我们通过调用DriverManager.getConnection()方法来建立数据库连接。然后,我们创建一个Statement对象并调用其executeQuery()方法来执行SQL查询。最后,我们循环遍历结果集并输出结果。最后,我们关闭所有打开的连接和资源。
我们大致可以总结为以下流程:
1.3.2 传统的JDBC有什么缺点?为什么要学mybatis框架?
- 按照理想的状态,如果我们想从数据库中获取数据,那么程序员最应该关注的就是SQL语句。但是使用JDBC的话,即使我们只想从数据库中做一条查询,我们就需要做注册驱动、获得数据库连接、获取执行SQL语句的Statement对象,释放资源等这些繁杂的工作。
- 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
- SQL语句在代码中硬编码,造成代码不易维护,实际应用SQL变化的可能较大,SQL变动需要改变 java 代码。
- 使用 preparedStatement 向占有位符号传参数存在硬编码,因为SQL语句的 where 条件不一定,可能多也可能少,修改SQL还要修改代码,系统不易维护。
- 对结果集解析存在硬编码(查询列名),SQL变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成 pojo 对象解析比较方便。
- 使用JDBC的话,需要我们自己手动地去把结果集和java对象对应起来,这个过程特别麻烦,使用框架的话,这些操作就不需要程序员处理了。
2. MyBatis打开方式
2.1 开发流程
2.2 开发示例
2.2.1 一般形式
首先,需要引入MyBatis的依赖包。可以使用Maven等构建工具,或手动下载jar包并添加到项目依赖中。
接下来,创建数据库表。本示例中将创建一张user表,包含id、name、age三个字段。
CREATE TABLE user (
id INT PRIMARY KEY,
name VARCHAR(20),
age INT
);
然后,在Java中定义用户实体类User,包含与user表对应的字段。
public class User {
private Integer id;
private String name;
private Integer age;
// Getters and setters
// ...
}
接下来,需要配置MyBatis的相关信息,包括数据库连接信息、映射文件等。本示例将采用XML方式配置。
在resources目录下创建mybatis-config.xml文件,配置数据源和mapper映射文件路径。
<configuration>
<!-- 数据源配置 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 映射文件配置 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
其中,配置了数据源连接信息和映射文件路径。
接下来,需要编写mapper映射文件。创建resources/mapper/UserMapper.xml文件,定义CRUD(增删改查)操作。
<mapper namespace="com.example.mapper.UserMapper">
<!-- 插入操作 -->
<insert id="insertUser" parameterType="com.example.pojo.User">
insert into user(id, name, age)
values(#{id}, #{name}, #{age})
</insert>
<!-- 删除操作 -->
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id}
</delete>
<!-- 更新操作 -->
<update id="updateUser" parameterType="com.example.pojo.User">
update user set name = #{name}, age = #{age}
where id = #{id}
</update>
<!-- 查询操作 -->
<select id="getUserById" resultType="com.example.pojo.User">
select id, name, age from user where id = #{id}
</select>
</mapper>
定义了插入、删除、更新和查询操作,映射方法名和参数都与Java中的UserMapper类对应。
接下来,编写UserMapper接口,定义与mapper映射文件对应的方法。
public interface UserMapper {
void insertUser(User user);
void deleteUser(int id);
void updateUser(User user);
User getUserById(int id);
}
然后,编写测试类,测试UserMapper的方法是否能正确操作数据库。
public class MyBatisTest {
public static void main(String[] args) {
// 创建SqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取UserMapper对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 执行操作
User user = new User();
user.setId(1);
user.setName("张三");
user.setAge(20);
userMapper.insertUser(user);
User user1 = userMapper.getUserById(1);
System.out.println(user1.toString());
user1.setName("李四");
userMapper.updateUser(user1);
user1 = userMapper.getUserById(1);
System.out.println(user1.toString());
userMapper.deleteUser(1);
// 提交事务
sqlSession.commit();
sqlSession.close();
}
}
以上代码根据操作顺序,依次执行插入、查询、更新、查询和删除操作,并提交事务。在控制台输出查询的结果,测试操作是否正确。
2.2.2 注解形式
下面是一个使用MyBatis注解进行开发的完整示例:
- 配置MyBatis
首先,需要在MyBatis的配置文件中添加扫描包的配置,以便找到注解:
<configuration>
<!-- 添加扫描包的配置 -->
<typeAliases>
<package name="com.example.model"/>
</typeAliases>
<mappers>
<mapper class="com.example.mapper.UserMapper"/>
</mappers>
</configuration>
- 创建数据表和实体类
假设我们要操作的是一个用户表,包含以下字段:
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`age` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
创建对应的实体类User:
public class User {
private Long id;
private String name;
private Integer age;
// 省略getter和setter方法
}
- 创建Mapper接口
接下来,我们需要创建一个Mapper接口类来定义SQL映射方法。使用注解方式的Mapper方法一般比XML方式简单,不需要手动写SQL语句,只需要使用注解来定义SQL语句即可。
@Mapper
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User findById(@Param("id") Long id);
@Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int save(User user);
@Update("UPDATE user SET name=#{name}, age=#{age} WHERE id = #{id}")
int update(User user);
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteById(@Param("id") Long id);
}
- 使用Mapper接口执行CRUD操作
现在我们可以使用Mapper接口的方法执行CRUD操作了:
@Autowired
private UserMapper userMapper;
public User getUserById(Long id) {
return userMapper.findById(id);
}
public int saveUser(User user) {
return userMapper.save(user);
}
public int updateUser(User user) {
return userMapper.update(user);
}
public int deleteUserById(Long id) {
return userMapper.deleteById(id);
}
- 测试
最后,我们可以编写一个简单的测试用例来测试上面的方法:
@Test
public void testCRUD() {
User user = new User();
user.setName("Tom");
user.setAge(18);
// 插入数据
userMapper.save(user);
// 查询数据
User result = userMapper.findById(user.getId());
assertThat(result.getName()).isEqualTo("Tom");
assertThat(result.getAge()).isEqualTo(18);
// 更新数据
user.setName("Jerry");
user.setAge(20);
userMapper.update(user);
// 查询更新后的数据
result = userMapper.findById(user.getId());
assertThat(result.getName()).isEqualTo("Jerry");
assertThat(result.getAge()).isEqualTo(20);
// 删除数据
userMapper.deleteById(user.getId());
// 查询数据
result = userMapper.findById(user.getId());
assertThat(result).isNull();
}
3. 项目实战
3.1 需求描述
使用MyBatis所学知识完成如下练习
- 使用c3p0的方式管理数据库连接
- 结合注解以及批处理方式向教师表(teacher)中添加500条数据
- 结合注解使用分页插件PageHelper进行分页查询,查询第3页的数据
教师表结构(参考)
字段 | 类型 | 说明 |
---|---|---|
id | int(11) | 教师id,主键,自增 |
name | varchar(20) | 姓名 |
sex | varchar(20) | 性别 |
j_no | int(11) | 工号 |
subject | varchar(20) | 所教学科 |
grade | varchar(20) | 所在年级 |
description | varchar(255) | 备注 |
3.2 项目开发
- 设计数据表t_teacher结构如下:
- 创建标准maven项目,添加依赖
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>4.6</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
</dependencies>
其中:
- mybatis/mysql-connector-java自不用多说
- junit是java单元测试框架
- pagehelper/jsqlparser是分页需要的依赖
- com.mchange则是c3p0的辅助包
-
设计文件层级如下:
-
完善实体类,写配置文件,调通数据库,编写DAO层测试
entity包下的Teacher类
package entity;
public class Teacher {
private Integer id;
private String name;
private String sex;
private Integer jNo;
private String subject;
private String grade;
private String description;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getjNo() {
return jNo;
}
public void setjNo(Integer jNo) {
this.jNo = jNo;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
resources包下的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>
<settings>
<!-- goods_id==>goodsId -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--设置数据库类型-->
<property name="helperDialect" value="mysql"/>
<!-- 分页合理化-->
<property name="reasonable" value="true"/>
</plugin>
</plugins>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="datasource.C3P0DataSourceFactory">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/school?useUnicode=true&
characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="user" value="root"/>
<property name="password" value="123456"/>
<property name="initialPoolSize" value="5"/>
<property name="maxPoolSize" value="20"/>
<property name="minPoolSize" value="5"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="dao"/>
</mappers>
</configuration>
utils包下的MyBatisUtils类
package utils;
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.Reader;
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory=null;
static {
Reader reader = null;
try {
reader = Resources.getResourceAsReader("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
throw new ExceptionInInitializerError(e);
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
public static SqlSession openSession(){
return sqlSessionFactory.openSession();
}
public static void closeSession(SqlSession session){
if(session!=null){
session.close();
}
}
}
datasource包下的C3P0DataSourceFactory类
package datasource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {
public C3P0DataSourceFactory(){
this.dataSource = new ComboPooledDataSource();
}
}
dao包下的TeacherDAO类
package dao;
import entity.Teacher;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface TeacherDAO {
@Insert("insert into t_teacher(name,sex,j_no,subject,grade,description) values " +
"(#{name} , #{sex} , #{jNo}, #{subject}, #{grade}, #{description})")
@SelectKey(statement = "select last_insert_id()",before = false,keyProperty = "id",resultType = Integer.class)
public int insert(Teacher teacher);
@Select("select * from t_teacher where id=#{id}")
public Teacher selectById(@Param("id") Integer id);
@Select("select * from t_teacher")
public List<Teacher> selectByPage();
@Insert({" <script> insert into t_teacher(name,sex,j_no,subject,grade,description) values " +
"<foreach collection='list' item='t' separator=','> " +
"(#{t.name},#{t.sex},#{t.jNo},#{t.subject},#{t.grade},#{t.description}) " +
"</foreach> </script>"})
public int batchInsert(@Param("list") List<Teacher> list);
@Delete({"<script>DELETE FROM t_teacher WHERE id in" +
"<foreach collection='list' item='item' index='index' open='(' close=')' separator=','>#{item}</foreach>" +
"</script>"})
public int batchDelete(@Param("list")List<Integer> list);
}
test包下的TeacherTest类
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import dao.TeacherDAO;
import entity.Teacher;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import utils.MyBatisUtils;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
public class TeacherTest {
@Test
public void testMyBatisUtils(){
SqlSession session= null;
try {
session = MyBatisUtils.openSession();
Connection connection = session.getConnection();
System.out.println(connection);
} catch (Exception e) {
e.printStackTrace();
}finally {
MyBatisUtils.closeSession(session);
}
}
@Test
public void testInsert(){
SqlSession session=null;
try {
session=MyBatisUtils.openSession();
TeacherDAO teacherDAO = session.getMapper(TeacherDAO.class);
Teacher teacher=new Teacher();
teacher.setName("张三");
teacher.setSex("男");
teacher.setjNo(20180001);
teacher.setSubject("数学");
teacher.setGrade("大1");
teacher.setDescription("为人和蔼");
teacherDAO.insert(teacher);
System.out.println(teacher.getId());
session.commit();
} catch (Exception e) {
if(session != null){
session.rollback();//回滚事务
}
e.printStackTrace();
}finally {
MyBatisUtils.closeSession(session);
}
}
@Test
public void testSelectById(){
SqlSession session= null;
try {
session = MyBatisUtils.openSession();
TeacherDAO teacherDAO = session.getMapper(TeacherDAO.class);
Teacher teacher=teacherDAO.selectById(1);
if (teacher!=null) {
System.out.println(teacher.getName());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
MyBatisUtils.closeSession(session);
}
}
@Test
public void testBatchInsert(){
SqlSession session=null;
try {
long st = new Date().getTime();
session=MyBatisUtils.openSession();
TeacherDAO teacherDAO = session.getMapper(TeacherDAO.class);
List<Teacher> list = new ArrayList();
for (int i=0;i<500;i++){
Teacher teacher=new Teacher();
teacher.setName("张三测试"+i);
teacher.setSex("男");
teacher.setjNo(20180002+i);
teacher.setSubject("数学");
teacher.setGrade("大"+(new Random().nextInt(4)+1));
teacher.setDescription("为人和蔼");
list.add(teacher);
}
teacherDAO.batchInsert(list);
session.commit();
long et = new Date().getTime();
System.out.println("执行时间:" + (et - st) + "毫秒");
} catch (Exception e) {
if(session != null){
session.rollback();//回滚事务
}
e.printStackTrace();
}finally {
MyBatisUtils.closeSession(session);
}
}
@Test
public void testBatchDelete(){
SqlSession session=null;
try {
long st = new Date().getTime();
session=MyBatisUtils.openSession();
TeacherDAO teacherDAO = session.getMapper(TeacherDAO.class);
List<Integer> list = new ArrayList();
for (int i=2001;i<=2501;i++){
list.add(i);
}
teacherDAO.batchDelete(list);
session.commit();
long et = new Date().getTime();
System.out.println("执行时间:" + (et - st) + "毫秒");
} catch (Exception e) {
if(session != null){
session.rollback();//回滚事务
}
e.printStackTrace();
}finally {
MyBatisUtils.closeSession(session);
}
}
@Test
public void testSelectByPage(){
SqlSession session = null;
try {
session = MyBatisUtils.openSession();
/*startPage方法会自动将下一次查询进行分页*/
PageHelper.startPage(3,30);
Page<Teacher> page = (Page) session.getMapper(TeacherDAO.class).selectByPage();
System.out.println("总页数:" + page.getPages());
System.out.println("总记录数:" + page.getTotal());
System.out.println("开始行号:" + page.getStartRow());
System.out.println("结束行号:" + page.getEndRow());
System.out.println("当前页码:" + page.getPageNum());
List<Teacher> data = page.getResult();//当前页数据
for (Teacher t:data) {
System.out.println(t.getName()+":"+t.getGrade());
}
} catch (Exception e) {
throw e;
} finally {
MyBatisUtils.closeSession(session);
}
}
}
至此我们便完成了项目需求的完整开发。
以上便是本文全部内容啦,希望能够带来帮助!