MyBatis学习笔记(上)
目录
MyBatis介绍
Mybatis简单示例
MyBatis编写流程
Mybatis参数
一.MyBatis参数详解
二.resultType
SqlMapConfig.xml配置文件
常见问题排查
MyBatis介绍
MyBatis 是一款轻量级 Java 持久层框架,核心价值是封装 JDBC 繁琐操作(如加载驱动、创建连接、处理结果集),让开发者聚焦 SQL 逻辑而非重复的 JDBC 代码。
核心设计思想
- 配置驱动:通过 XML 或注解定义 SQL 语句,避免硬编码。
- 参数映射:自动将 Java 对象与 SQL 动态参数绑定(如
#{username}
)。 - 结果映射:执行 SQL 后,自动将数据库结果集转换为 Java 对象(ORM 思想)。
- 低侵入性:不强制依赖复杂配置,可灵活集成到各类项目中。
Mybatis简单示例
1.创建库和表结构:
-- 1. 创建数据库
create database mybatis_db;
use mybatis_db;-- 2. 创建用户表
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID(自增)',`username` varchar(32) NOT NULL COMMENT '用户名称',`birthday` datetime DEFAULT NULL COMMENT '生日',`sex` char(1) DEFAULT NULL COMMENT '性别(男/女)',`address` varchar(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- 3. 插入测试数据
insert into `user`(`id`,`username`,`birthday`,`sex`,`address`)
values(1,'老王','2018-02-27 17:47:08','男','北京'),
(2,'熊大','2018-03-02 15:09:37','女','上海'),(3,'熊二','2018-03-04 11:34:34','女','深圳'),(4,'光头强','2018-03-04 12:04:06','男','广州');
2.所需依赖:
<dependencies><!-- 1. MyBatis 核心依赖 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.5</version> <!-- 稳定版本,兼容性好 --></dependency><!-- 2. MySQL 驱动(适配 MySQL 5.x;若用 MySQL 8.x,需改为 8.0+ 版本,驱动类为 com.mysql.cj.jdbc.Driver) --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version></dependency><!-- 3. 单元测试(JUnit 4,简化测试代码) --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope> <!-- 仅测试环境生效 --></dependency><!-- 4. 日志(Log4j,打印 SQL 执行日志,便于调试) --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency>
</dependencies>
补充:Log4j 配置(可选)
在 resources
目录下创建 log4j.properties
,开启 SQL 日志打印:
# 日志输出级别:DEBUG(打印 SQL 细节)、INFO(仅关键信息)
log4j.rootLogger=DEBUG, Console# 控制台输出配置
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
# 日志格式:时间 | 线程 | 级别 | 类名 | 日志内容
log4j.appender.Console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c - %m%n# MyBatis 日志细化(可选)
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
3.编写User实现类:
package com.mszlu.domain;import java.util.Date;/*** 用户实体类(对应数据库 user 表)*/
public class User {private Integer id; // 对应表 id 列(自增)private String username; // 对应表 username 列private Date birthday; // 对应表 birthday 列(MyBatis 自动转换 datetime ↔ Date)private String sex; // 对应表 sex 列private String address; // 对应表 address 列// 无参构造器(MyBatis 反射创建对象时必须)public User() {}// 有参构造器(便于手动创建对象)public User(String username, Date birthday, String sex, String address) {this.username = username;this.birthday = birthday;this.sex = sex;this.address = address;}// Getter + Setter(MyBatis 需通过 Setter 注入结果集数据)public Integer getId() { return id; }public void setId(Integer id) { this.id = id; }public String getUsername() { return username; }public void setUsername(String username) { this.username = username; }public Date getBirthday() { return birthday; }public void setBirthday(Date birthday) { this.birthday = birthday; }public String getSex() { return sex; }public void setSex(String sex) { this.sex = sex; }public String getAddress() { return address; }public void setAddress(String address) { this.address = address; }// toString(便于打印对象信息)@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", birthday=" + birthday +", sex='" + sex + '\'' +", address='" + address + '\'' +'}';}
}
4.编写userMapper以及userMapper.xml
userMapper:定义接口
userMapper.xml(在resources目录下,创建mapper文件夹。编写UserMapper.xml的配置文件):定义sql语句
5.编写主配置文件SqlMapConfig.xml,SqlMapConfig.xml是MyBatis框架的核心配置文件。在resources目录下创建SqlMapConfig.xml的配置文件(名称可以任意),导入对应的约束,编写主配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!-- 配置环境们 --><environments default="mysql"><!-- 配置具体的环境 --><environment id="mysql"><!-- 配置事务管理类型 --><transactionManager type="JDBC"/><!-- 配置是否需要使用连接池,POOLED使用,UNPOOLED不使用 --><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql:///mybatis_db"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><!-- 加载映射的配置文件 --><mappers><mapper resource="mapper/UserMapper.xml"/></mappers>
</configuration>
6.编写测试方法
public class testDemo1 {@Testpublic void testFindAll() throws IOException {// 加载主配置文件,目的是构建SqlSessionFactory的对象InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");// 创建SqlSessionFactory对象SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);// 使用SqlSessionFactory工厂对象创建SqlSession对象SqlSession session = factory.openSession();// 通过session创建UserMapper接口的代理对象UserMapper mapper = session.getMapper(UserMapper.class);// 调用查询所有的方法//第一种方式:List<User> list = mapper.findAll();//第二种方式
// List<User> userList = session.selectList("com.mszlu.mapper.UserMapper.findAll");// 遍历集合for (User user : list) {System.out.println(user);}// 释放资源session.close();in.close();}
}
MyBatis编写流程
MyBatis 推荐使用 “接口 + XML” 的方式定义 SQL:Mapper 接口
定义方法签名,Mapper XML
定义具体 SQL 语句,两者通过全限定名 + 方法名关联(无需手动实现接口,MyBatis 动态生成代理对象)。
使用Dao进行增删改查
1.创建userMapper接口
package com.mszlu.mapper;import com.mszlu.domain.QueryVo;
import com.mszlu.domain.User;
import java.util.List;/*** User 表 Mapper 接口(定义 SQL 方法签名)*/
public interface UserMapper {// 1. 查询所有用户List<User> findAll();// 2. 测试列名与属性名不一致(需用 resultMap 映射)List<User> findAll2();// 3. 按 ID 查询用户(参数为简单类型:Integer)User findById(Integer userId);// 4. 新增用户(返回自增 ID,参数为 POJO:User)void insert(User user);// 5. 修改用户(参数为 POJO:User)void update(User user);// 6. 删除用户(参数为简单类型:Integer)void delete(Integer userId);// 7. 模糊查询用户(参数为简单类型:String)List<User> findByName(String username);// 8. 查询用户总数(返回简单类型:Integer)Integer findByCount();// 9. 按包装类查询(参数为包装类:QueryVo)List<User> findByVo(QueryVo queryVo);
}
2.在resources/mapper
目录下创建 UserMapper.xml
,编写 SQL 语句与结果映射:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace:必须与 Mapper 接口的全限定名一致(关联接口与 XML)例:com.mszlu.mapper.UserMapper-->
<mapper namespace="com.mszlu.mapper.UserMapper"><!-- 1. 结果映射(resultMap):解决“表列名与实体类属性名不一致”问题 --><!-- id:resultMap 的唯一标识(供 select 标签的 resultMap 属性引用)type:映射的实体类类型(可直接用别名 user,对应 com.mszlu.domain.User)--><resultMap id="userMap" type="user"><!-- id 标签:映射主键列(性能优化,可选但推荐)result 标签:映射普通列property:实体类属性名column:表列名(或 SQL 查询的列别名)--><id property="id" column="id"/> <!-- 若列名与属性名一致,可省略该配置 --><result property="username" column="_username"/> <!-- 列别名为 _username,对应属性 username --><result property="birthday" column="birthday"/><result property="sex" column="sex"/><result property="address" column="address"/></resultMap><!-- 2. 查询所有用户(列名与属性名一致,用 resultType) --><!-- id:必须与 Mapper 接口的方法名一致(findAll)resultType:返回结果类型(实体类别名或全限定名,MyBatis 自动映射)--><select id="findAll" resultType="user">select * from user;</select><!-- 3. 查询所有用户(列名与属性名不一致,用 resultMap) --><!-- SQL 中 username 列取别名为 _username,需通过 userMap 映射到属性 username --><select id="findAll2" resultMap="userMap">select id, username _username, birthday, sex, address from user;</select><!-- 4. 按 ID 查询用户(简单参数,用 #{占位符}) --><!-- parameterType:参数类型(简单类型可省略,MyBatis 自动推断)#{id}:占位符,对应方法参数(简单类型时,占位符名称可任意,如 #{userId} 也可)--><select id="findById" parameterType="int" resultType="user">select * from user where id = #{id};</select><!-- 5. 新增用户(返回自增 ID) --><!-- <selectKey>:获取新增后的自增 ID(MySQL 用 last_insert_id() 函数)keyProperty:将自增 ID 注入到实体类的哪个属性(User.id)order:执行时机(AFTER:插入后执行,BEFORE:插入前执行)resultType:返回 ID 的类型--><insert id="insert" parameterType="user"><selectKey keyProperty="id" order="AFTER" resultType="int">select last_insert_id(); <!-- MySQL 系统函数:返回当前会话最后一次插入的自增 ID --></selectKey>insert into user (username, birthday, sex, address) values (#{username}, #{birthday}, #{sex}, #{address});</insert><!-- 6. 修改用户 --><update id="update" parameterType="user">update user set username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} where id=#{id};</update><!-- 7. 删除用户 --><delete id="delete" parameterType="int">delete from user where id = #{id};</delete><!-- 8. 模糊查询用户(两种方式:#{} 与 ${}) --><!-- 方式1:#{}(推荐,防 SQL 注入,需手动加 %) --><select id="findByName" parameterType="string" resultType="user">select * from user where username like #{username}; <!-- 调用时需传参数:"%王%"(如 mapper.findByName("%王%")) --></select><!-- 方式2:${}(不防 SQL 注入,直接拼接字符串,占位符只能是 ${value}) --><!-- <select id="findByName" parameterType="string" resultType="user">select * from user where username like '${value}';<!-- 调用时需传参数:"%王%" 或 "王%"(如 mapper.findByName("%王%")) --></select>--><!-- 9. 查询用户总数(返回简单类型) --><select id="findByCount" resultType="int">select count(*) from user; <!-- count(*) 返回 int 类型 --></select><!-- 10. 按包装类查询(嵌套参数,用 #{对象.属性}) --><!-- parameterType:包装类类型(QueryVo),通过 #{user.username} 引用嵌套对象的属性 --><select id="findByVo" parameterType="queryVo" resultType="user">select * from user where username = #{user.username};</select></mapper>
3.测试代码...
import com.mszlu.domain.QueryVo;
import com.mszlu.domain.User;
import com.mszlu.mapper.UserMapper;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.List;public class UserTest {private InputStream in;private SqlSession session;private UserMapper mapper;@Beforepublic void init() throws IOException {in = Resources.getResourceAsStream("SqlMapConfig.xml");SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in) ;session = factory.openSession();mapper = session.getMapper(UserMapper.class);}@Afterpublic void destory() throws IOException {session.close();in.close();}@Testpublic void testFindAll() throws IOException {List<User> userList = mapper.findAll();for(User user : userList){System.out.println(user);}in.close();}@Testpublic void testFindAll2() throws IOException {List<User> userList = mapper.findAll2();for(User user : userList){System.out.println(user);}in.close();}@Testpublic void testFindById() throws Exception {User user = mapper.findById(4);System.out.println(user);in.close();}@Testpublic void testInsert() throws Exception {User user = new User();user.setUsername("美美2");user.setBirthday(new Date());user.setSex("男");user.setAddress("顺义");mapper.insert(user);session.commit();System.out.println(user.getId());}@Testpublic void testUpdate() throws Exception {User user = mapper.findById(4);user.setUsername("小凤");mapper.update(user);session.commit();}@Testpublic void testDelete() throws Exception {mapper.delete(5);session.commit();}// 第一种@Testpublic void testFindByName() throws Exception {List<User> list = mapper.findByName("%王%");for (User user : list) {System.out.println(user);}}// 第二种@Testpublic void testFindByName2() throws Exception {List<User> list = mapper.findByName("王");for (User user : list) {System.out.println(user);}}@Testpublic void testFindByCount() throws Exception {Integer count = mapper.findByCount();System.out.println("总记录数:"+count);}//包装类测试@Testpublic void testFindByVo() throws Exception {QueryVo queryVo = new QueryVo();User user = new User();user.setUsername("熊大");queryVo.setUser(user);List<User> userList = mapper.findByVo(queryVo);for (User user1 : userList) {System.out.println(user1);}}
}
模糊查询符号使用的区别
通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
通过$可以将传入的内容拼接在中且不进行类型转换,${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。
特性 |
|
|
本质 | PreparedStatement 占位符( ) | 字符串直接拼接 |
类型转换 | 自动进行 Java 类型 ↔ JDBC 类型转换 | 不转换,直接拼接字符串 |
SQL 注入防护 | 支持(占位符不直接拼接 SQL) | 不支持(直接拼接,有注入风险) |
简单类型参数名称 | 可任意(如 、 ) | 只能是 |
适用场景 | 普通参数传递(推荐) | 动态表名、动态排序字段(谨慎使用) |
Mybatis参数
一.MyBatis参数详解
1、简单数据类型:java八大类+String(mybatis把String归为简单数据类型)
简单的写法:java.lang.Integer --> int integer Int Integer 都可以,框架提供简写的方式。
2、POJO(JavaBean实体类)对象类型,默认不能简写,可以配置
例子如:User对象
3、POJO包装对象类型,包含更多的实体类(即对象中引用其他对象)
如在QueryVo实体类中:包含了User对象
在mapper接口中,加入这个函数
public List<User> findByVo(QueryVo queryVo);
实现:
参数类型是QueryVo类,我们可以直接使用二级子类,比如user,name,role等,然后使用user里的属性再进行查询
<!-- 根据封装类型来查询--><select id="findByVo" resultType="com.mszlu.domain.User" parameterType="com.mszlu.domain.QueryVo" >select * from user where username = #{user.username}</select>
测试类:
//包装类测试
@Test
public void testFindByVo() throws Exception {QueryVo queryVo = new QueryVo();User user = new User();user.setUsername("熊大");queryVo.setUser(user);List<User> userList = mapper.findByVo(queryVo);for (User user1 : userList) {System.out.println(user1);}
}
二.resultType
1.返回简单数据类型
int double long(Java八大基本类型) String
2.返回POJO数据类型
返回User对象类型
3.resultMap结果类型
resultType 用法:
直接指定一个实体类(比如 User),查询结果会自动装进这个类的对象里。
但要求:数据库查出来的列名(比如表的 username 列)和 实体类的属性名(比如 User 类的 username 属性)必须一模一样,才能对应上。
resultMap 用法:
当列名和属性名不一样时(比如表列是 user_name,类属性是 userName),用它来手动指定 “列名对应哪个属性名”,帮它们搭个桥。
另外,它还能处理复杂的情况:比如实体类里不仅有基本属性,还包含另一个实体类(一对一)或者一个列表(一对多),也能正确映射进去。
<!--配置resultMap,用来进行数据封装id="唯一的名称,用来被引用的"type="进行封装数据的类型"
-->
<resultMap id="userMap" type="com.qcbyjy.domain.User"><!--property="JavaBean中的属性"column="表中的字段"--><result property="id" column="_id"/><result property="username" column="_username" /><result property="birthday" column="_birthday" /><result property="sex" column="_sex" /><result property="address" column="_address" />
</resultMap>
对于sql语句:
select id, username _username, birthday, sex, address from user;
实际查询的字段是username,但返回的结果的字段是别名_username
所以当我数据库表结构如下:
而查询的sql语句以及resultMap映射是上面所写的时
sql语句返回的结果集中,所对应字段名字是id,_username,birthday,sex,address,在结果集中查找,只有_username可以对上,所以只有_username才有结果,其他都为空
SqlMapConfig.xml配置文件
1.1. 定义properties标签的方式管理数据库的信息
即把数据库信息定义properties标签中的方法,然后dataSource再使用
1.2在项目中定义jdbc.properties属性文件,存储数据库相关的信息,统一管理
1.2.1 jdbc.properties属性文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///mybatis_db
jdbc.username=root
jdbc.password=123456
1.2.2 编写sql配置文件
这样少了前面的<properties>标签中的配置
2、类型别名定义
- MyBatis自已有类型别名的注册类,编写int或者integer通过注册可以找到java.lang.Integer
- 我们自己也可以进行别名的注册
- SqlMapConfig.xml的配置文件
<!-- 定义别名 -->
<typeAliases><!-- 把com.mszlu.domain.User使用user别名来显示,别名user User USER都可以,默认是忽略大写的 --><typeAlias type="com.mszlu.domain.User" alias="user"/><!-- 针对com.mszlu.domain包下的所有的类,都可以使用当前的类名做为别名 --><package name="com.mszlu.domain"/>
</typeAliases>
(注意xml中顺序, MyBatis 配置文件的标签顺序不符合规范。MyBatis 对 <configuration>
内部子标签的顺序有严格要求,必须按照官方指定的顺序排列,否则会报格式错误。)
官方规定的 <configuration>
子标签顺序为(按先后排列):
properties? → settings? → typeAliases? → typeHandlers? → objectFactory? → objectWrapperFactory? → reflectorFact
此时就可以成功修改了。
常见问题排查
ClassNotFoundException
(类找不到):检查 Mapper 接口全限定名、实体类路径是否与 XML 中namespace
、resultType
一致。BindingException
(接口与 XML 绑定失败):检查 XML 中id
是否与接口方法名一致,namespace
是否与接口全限定名一致。SQLSyntaxErrorException
(SQL 语法错误):打印 SQL 日志(Log4j),检查生成的 SQL 是否正确(如参数拼接、表名 / 列名是否存在)。- MySQL 8.x 驱动报错:需将驱动版本改为
8.0+
,驱动类改为com.mysql.cj.jdbc.Driver
,URL 加时区参数?serverTimezone=UTC