MyBatis 简介
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
获取 MyBatis
Maven 依赖:
< dependency >
< groupId > org.mybatis </ groupId >
< artifactId > mybatis </ artifactId >
< version > 3.5.9 </ version >
</ dependency >
GitHub: https://github.com/mybatis/mybatis-3/releases
为什么使用 MyBatis?
简单易学 本身很小且简单,没有任何第三方依赖,易于学习和使用。
灵活 SQL 写在 XML 里,便于统一管理和优化,可以满足所有数据库操作需求。
解耦 通过 DAO 层分离业务逻辑和数据访问逻辑,提高可维护性。
强大的映射 提供映射标签,支持对象与数据库的 ORM 字段关系映射。
快速开始
开发流程
引入依赖
在 pom.xml 中添加 MyBatis 和数据库驱动依赖。
创建配置文件
创建 mybatis-config.xml 核心配置文件。
创建 Mapper
创建 Mapper 接口和 XML 映射文件。
初始化 SessionFactory
创建 SqlSessionFactory 工具类。
执行操作
创建 SqlSession 对象执行数据库操作。
核心配置文件
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/mybatis?characterEncoding=utf8 & useSSL=false & serverTimezone=UTC" />
< property name = "username" value = "root" />
< property name = "password" value = "root" />
</ dataSource >
</ environment >
</ environments >
<!-- Mapper 映射 -->
< mappers >
< mapper resource = "mappers/UserMapper.xml" />
</ mappers >
</ configuration >
dataSource 的 type="POOLED" 表示使用连接池,sqlSession.close() 时会将连接放回连接池而不是关闭。
创建工具类
MyBatisUtils.java:
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory = null ;
// 静态代码块初始化
static {
try {
Reader reader = Resources . getResourceAsReader ( "mybatis-config.xml" );
sqlSessionFactory = new SqlSessionFactoryBuilder (). build (reader);
} catch ( IOException e ) {
e . printStackTrace ();
throw new ExceptionInInitializerError (e);
}
}
// 获取 SqlSession
public static SqlSession getSqlSession () {
return sqlSessionFactory . openSession ();
}
// 关闭 SqlSession
public static void closeSession ( SqlSession sqlSession ) {
if (sqlSession != null ) {
sqlSession . close ();
}
}
}
实体类
public class User {
private Integer id ;
private String name ;
private String password ;
// Getter and Setter
// toString()
}
Mapper 映射文件
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 = "UserMapper" >
< select id = "selectAll" resultType = "User" >
SELECT * FROM user
</ select >
< select id = "selectById" parameterType = "Integer" resultType = "User" >
SELECT * FROM user WHERE id = #{id}
</ select >
</ mapper >
测试代码
@ Test
public void test () {
SqlSession sqlSession = null ;
try {
sqlSession = MyBatisUtils . getSqlSession ();
List < User > users = sqlSession . selectList ( "UserMapper.selectAll" );
for ( User user : users) {
System . out . println (user);
}
} catch ( Exception e ) {
e . printStackTrace ();
} finally {
MyBatisUtils . closeSession (sqlSession);
}
}
CRUD 操作
查询操作
查询所有:
< select id = "selectAll" resultType = "User" >
SELECT * FROM user
</ select >
根据 ID 查询:
< select id = "selectById" parameterType = "Integer" resultType = "User" >
SELECT * FROM user WHERE id = #{id}
</ select >
插入操作
< insert id = "insert" parameterType = "User" >
INSERT INTO user(name, password)
VALUES (#{name}, #{password})
<!-- 返回插入的主键ID -->
< selectKey resultType = "Integer" keyProperty = "id" order = "AFTER" >
SELECT LAST_INSERT_ID()
</ selectKey >
</ insert >
使用:
User user = new User ();
user . setName ( "test" );
user . setPassword ( "123456" );
int count = sqlSession . insert ( "UserMapper.insert" , user);
sqlSession . commit ();
System . out . println ( "插入的ID: " + user . getId ());
增删改操作需要提交事务: sqlSession.commit()
更新操作
< update id = "updateUser" parameterType = "User" >
UPDATE user
SET name = #{name}, password = #{password}
WHERE id = #{id}
</ update >
删除操作
< delete id = "deleteUser" parameterType = "int" >
DELETE FROM user WHERE id = #{id}
</ delete >
参数传递
使用 Map 传递参数
当实体类参数过多时,可以使用 Map:
< insert id = "insert" parameterType = "java.util.Map" >
INSERT INTO user (id, name, password)
VALUES (#{id}, #{name}, #{password})
</ insert >
调用:
Map < String , Object > param = new HashMap <>();
param . put ( "id" , 8 );
param . put ( "name" , "aaa" );
param . put ( "password" , "1212121" );
sqlSession . insert ( "UserMapper.insert" , param);
sqlSession . commit ();
参数规则
Map 取 key: parameterType="map"
对象取属性名: parameterType="Object"
基本类型可以随便写,可省略 parameterType
多个参数用 Map 或注解
Map 作为结果集
< select id = "selectAllMap" resultType = "java.util.LinkedHashMap" >
SELECT * FROM user
</ select >
使用 LinkedHashMap 可以保持字段的插入顺序,HashMap 会按 hash 值排序。
ResultMap
ResultMap 用于复杂的结果映射,特别是字段名与属性名不一致时。
基本用法
< resultMap id = "rmUser" type = "User" >
< id property = "id" column = "id" />
< result property = "name" column = "user_name" />
< result property = "password" column = "user_password" />
</ resultMap >
< select id = "selectAllMap" resultMap = "rmUser" >
SELECT id, user_name, user_password FROM user
</ select >
ResultMap 元素
id: 主键映射
result: 普通字段映射
association: 一对一关联
collection: 一对多关联
discriminator: 鉴别器映射
批量操作
批量插入
< insert id = "batchInsert" parameterType = "java.util.List" >
INSERT INTO blog VALUES
< foreach collection = "list" item = "item" index = "index" separator = "," >
(#{item.id}, #{item.title}, #{item.author}, #{item.createTime}, #{item.views})
</ foreach >
</ insert >
使用:
List < Blog > blogs = new ArrayList <>();
for ( int i = 0 ; i < 5 ; i ++ ) {
Blog blog = new Blog ();
blog . setId (i + 1 );
blog . setTitle ( "测试" + i);
blogs . add (blog);
}
sqlSession . insert ( "BlogMapper.batchInsert" , blogs);
sqlSession . commit ();
批量插入速度最快,但不能获得插入数据的 ID,且 SQL 太长可能被服务器拒绝。
SQL 注入防护
# vs $
# (推荐):
解析为 JDBC 预编译语句的参数标记符 ?
预编译处理,防止 SQL 注入
变量替换在 DBMS 中进行
< select id = "selectByName" parameterType = "String" resultType = "User" >
SELECT * FROM user WHERE name = #{name}
</ select >
解析为: SELECT * FROM user WHERE name = ?
$ :
纯字符串替换
动态 SQL 解析阶段进行变量替换
存在 SQL 注入风险
< select id = "selectByName" parameterType = "String" resultType = "User" >
SELECT * FROM user WHERE name = '${name}'
</ select >
当 name = "'; DROP TABLE user; --" 时会导致 SQL 注入!
使用建议
能使用 # 的地方就用 #,只有在表名、字段名等无法使用预编译的地方才使用 $。
必须使用 $ 的情况:
<!-- 表名作为变量 -->
< select id = "selectFromTable" parameterType = "String" resultType = "Map" >
SELECT * FROM ${tableName}
</ select >
配置详解
属性配置
引入外部配置文件:
< properties resource = "db.properties" />
db.properties:
driver =com.mysql.cj.jdbc.Driver
url =jdbc:mysql://localhost:3306/mybatis
username =root
password =root
使用:
< dataSource type = "POOLED" >
< property name = "driver" value = "${driver}" />
< property name = "url" value = "${url}" />
< property name = "username" value = "${username}" />
< property name = "password" value = "${password}" />
</ dataSource >
类型别名
单个配置:
< typeAliases >
< typeAlias type = "com.example.pojo.User" alias = "User" />
</ typeAliases >
包扫描:
< typeAliases >
< package name = "com.example.pojo" />
</ typeAliases >
使用注解:
@ Alias ( "User" )
public class User {
// ...
}
常用设置:
< settings >
<!-- 开启驼峰命名映射 -->
< setting name = "mapUnderscoreToCamelCase" value = "true" />
<!-- 开启二级缓存 -->
< setting name = "cacheEnabled" value = "true" />
<!-- 延迟加载 -->
< setting name = "lazyLoadingEnabled" value = "true" />
</ settings >
Mapper 注册
< mappers >
< mapper resource = "mappers/UserMapper.xml" />
</ mappers >
< mappers >
< mapper class = "com.example.mapper.UserMapper" />
</ mappers >
< mappers >
< package name = "com.example.mapper" />
</ mappers >
日志配置
Logback 配置
添加依赖:
< dependency >
< groupId > ch.qos.logback </ groupId >
< artifactId > logback-classic </ artifactId >
< version > 1.2.7 </ version >
</ dependency >
logback.xml:
<? xml version = "1.0" encoding = "UTF-8" ?>
< configuration >
< appender name = "console" class = "ch.qos.logback.core.ConsoleAppender" >
< encoder >
< pattern >
[%thread] %d{HH:mm:ss.SSS} %-5level %logger{10} - %msg%n
</ pattern >
</ encoder >
</ appender >
<!-- 日志级别: error > warn > info > debug > trace -->
< root level = "debug" >
< appender-ref ref = "console" />
</ root >
</ configuration >
MyBatis 日志设置
标准日志:
< settings >
< setting name = "logImpl" value = "STDOUT_LOGGING" />
</ settings >
SLF4J:
< settings >
< setting name = "logImpl" value = "SLF4J" />
</ settings >
作用域和生命周期
SqlSessionFactoryBuilder
一旦创建了 SqlSessionFactory 就不再需要
应该是局部变量
SqlSessionFactory
一旦被创建就应该在应用运行期间一直存在
使用单例模式或静态单例模式
最佳作用域是应用作用域
SqlSession
连接到数据库的一个线程
不是线程安全的,不能被共享
最佳作用域是请求或方法作用域
用完必须关闭,建议放在 finally 块中
连接池配置
配置 C3P0
添加依赖:
< dependency >
< groupId > com.mchange </ groupId >
< artifactId > c3p0 </ artifactId >
< version > 0.9.5.5 </ version >
</ dependency >
自定义数据源工厂:
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {
public C3P0DataSourceFactory () {
this . dataSource = new ComboPooledDataSource ();
}
}
配置:
< dataSource type = "com.example.datasource.C3P0DataSourceFactory" >
< property name = "driverClass" value = "com.mysql.cj.jdbc.Driver" />
< property name = "jdbcUrl" value = "jdbc:mysql://localhost:3306/mybatis" />
< property name = "user" value = "root" />
< property name = "password" value = "root" />
< property name = "initialPoolSize" value = "5" />
</ dataSource >
最佳实践
使用接口方式 推荐使用 Mapper 接口方式,而不是命名空间字符串调用。
防止 SQL 注入 优先使用 # 进行参数绑定,避免使用 $。
规范命名 Mapper 接口和 XML 文件名称保持一致,便于维护。
常见问题
检查:
XML 文件的 namespace 是否与接口全限定名一致
XML 文件是否已在 mybatis-config.xml 中注册
方法名是否与 XML 中的 id 一致
单个参数: 多个参数:
使用 @Param 注解指定参数名
使用 Map 传递参数
检查:
SQL 语句是否正确
字段名与属性名是否匹配
使用 ResultMap 自定义映射
增删改操作必须调用: 或开启自动提交: SqlSession sqlSession = sqlSessionFactory . openSession ( true );