MyBatis学习笔记
相关学习视频:动力节点MyBatis教程实战精讲
官方中文文档:MyBatis 3
作用
减轻使用 JDBC 的复杂性,不用编写重复的创建 Connetion , Statement ; 不用编写关闭资源代码。
直接使用 java 对象,表示结果数据。让开发者专注 SQL 的处理。 其他分心的工作由 MyBatis 代劳。
使用
准备
创建一个表
CREATE TABLE `student` (
`id` int(11) NOT NULL ,
`name` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
创建项目
使用IDEA创建一个普通Maven项目。
加入如下依赖(版本自行修改):
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!--加个lombok方便用-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
Maven 默认会过滤掉其他格式的文件,无法打包 XML 格式的文件到输出中,所以需要手动加入:
<build>
<resources>
<resource>
<!--所在的目录-->
<directory>src/main/java</directory>
<!--包括目录下的.properties,.xml 文件都会扫描到-->
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
修改配置
在 resources/
目录下创建一个 XML 文件,例如 mybatis.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">
<!--mybatis的事务类型-->
<!--表示用JDBC中Connection对象的commit, rollback做事务处理-->
<transactionManager type="JDBC"/>
<!--表示数据源,连接数据库的 type 表示数据源的类型 POOLED 表示使用连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://test.com:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="abc123"/>
</dataSource>
</environment>
</environments>
<!--SQL映射文件 SQL Mapper 的位置-->
<mappers>
<!--一个mapper标签指定一个文件的位置-->
<!--从类路径开始的路径信息-->
<mapper resource="cn/ethy/dao/StudentDao.xml"/>
</mappers>
</configuration>
入门案例
创建实体类
package entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@ToString
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private int id;
private String name;
private String email;
private int age;
}
DAO
StudentDao.java
:编写接口
package cn.ethy.dao;
import cn.ethy.entity.Student;
import java.util.List;
public interface StudentDao {
List<Student> selectStudents();
}
StudentDao.xml
:编写SQL语言
<?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="cn.ethy.dao.StudentDao">
<select id="selectStudents" resultType="cn.ethy.entity.Student">
SELECT * FROM student ORDER BY id
</select>
</mapper>
主程序
Test.java
:主程序
public class Test {
@Test
public void test01() throws IOException {
// 1. 定义MyBatis主配置文件的名称
String config = "mybatis.xml";
// 2. 读取这个 config 表示的文件
InputStream in = Resources.getResourceAsStream(config);
// 3. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 4. 创建SqlSessionFactory对象
SqlSessionFactory factory = builder.build(in);
// 5. 从SqlSessionFactory中获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 6. 指定要执行的SQL语句的标识 SQL映射文件中的namespace + "." + 标签的ID值
String sqlId = "cn.ethy.dao.StudentDao.selectStudents";
// 7. 执行SQL语句,通过SQL ID找到语句
List<Student> studentList = sqlSession.selectList(sqlId);
// 8. 输出结果
studentList.forEach(System.out::println);
// 9. 关闭SQLSession
sqlSession.close();
}
}
是不是又臭又长?
循序学习
插入数据
StudentDao.java
:编写接口
// 插入
int insertStudent(Student student);
StudentDao.xml
:编写SQL语言
<insert id="insertStudent">
INSERT INTO student VALUES(#{id}, #{name}, #{email}, #{age})
</insert>
注意这里用到了 ${}
的形式来获取参数,这里的值应该和实体类中的变量名称相同,因为他是用对应名称的Get方法来操作的,如果错了,就会报错:
Error updating database. Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'mail' in 'class cn.ethy.entity.Student'
大小写也有影响。
Test.java
: 测试文件
int i = sqlSession.insert(sqlId,
new Student(1111, "吕布", "asdf@aaa.com", 12));
// 提交后才能看到
sqlSession.commit();
开启日志
在 mybatis.xml
中 <configuration>
标签下添加如下配置:
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
用到的类
Resources
: mybatis中的一个类,负责读取主配置文件
InputStream in = Resources.getResourceAsStream(config);
SqlSessionFactoryBuilder
: 创建SqlSessionFactory对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
SqlSessionFactory
: 用来获取SqlSession对象。重量级对象,程序创建一个对象耗时比较长,使用的资源比较多,在整个项目中,有一个就够了。
SqlSessionFactory本身是一个接口,下面有许多实现类
SqlSession sqlSession = factory.openSession();
// 可以携带一个参数,表示是否开启自动提交,默认不会自动提交
SqlSession sqlSession = factory.openSession(true);
SqlSession
: SqlSession接口定义了操作数据的方法,如 selectOne
、 update
等;
实现类有:DefaultSqlSession。
注意:SqlSession 接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其 close()
方法,将其关闭。再次需要会话,再次创建。 SqlSession 在方法内部创建,使用完毕后关闭。
封装工具类
utils.MyBatisUtil.java
:确保只有一个 SqlSession:
package cn.ethy.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.InputStream;
public class MyBatisUtil {
private static SqlSessionFactory factory = null;
static {
String config = "mybatis.xml";
try {
InputStream in = Resources.getResourceAsStream(config);
factory = new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
SqlSession sqlSession = null;
if (factory != null) {
sqlSession = factory.openSession();
}
return sqlSession;
}
}
DAOImpl
前面 XXXDao.java
的接口并没有用上,这里尝试完成他们的实现类,如:
StudentDaoImpl.java
:
package cn.ethy.dao.impl;
import cn.ethy.dao.StudentDao;
import cn.ethy.entity.Student;
import cn.ethy.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class StudentDaoImpl implements StudentDao {
@Override
public List<Student> selectStudents() {
SqlSession session = MyBatisUtil.getSqlSession();
List<Student> studentList = session.selectList("cn.ethy.dao.StudentDao.selectStudents");
session.close();
return studentList;
}
@Override
public int insertStudent(Student student) {
SqlSession session = MyBatisUtil.getSqlSession();
int result = session.insert("cn.ethy.dao.StudentDao.insertStudent", student);
session.close();
return result;
}
}
Test.java
:
@Test
public void test03() {
StudentDao studentDao = new StudentDaoImpl();
studentDao.insertStudent(new Student(2921, "马克", "asdf@allx.com", 22));
studentDao.selectStudents().forEach(System.out::println);
session.close();
}
如果这样的话,有很多步骤都是重复的,或者说是有某种规律的,那么我们能否把这种规律找出来呢?
Mapper
其实,我们无需自己编写实现类,我们在XML文件中已经提供了很多信息,利用这些信息,MyBatis可以利用动态代理机制自动生成对应的实现文件:
@Test
public void test04() {
SqlSession session = MyBatisUtil.getSqlSession();
StudentDao dao = session.getMapper(StudentDao.class);
dao.selectStudents().forEach(System.out::println);
session.close();
}
传递参数
parameterType
:告诉MyBatis返回值的类型
<select id="selectStudents" resultType="cn.ethy.entity.Student" parameterType="java.lang.Integer">
SELECT * FROM student WHERE id = #{id}
</select>
由于一些常用的类型这样太长了,所以这些类型有简称(别名)(点击查看更多别名)
所以上面你也可以写成 int
或 Integer
另外,这个参数也不是强制的, mybatis可以通过反射机制发现接口参数的类型,所以可以没有。一般我们也不写。
一个简单参数
简单: 基础类型及其包装类,以及String类型
占位符: #{任意非空字符}
多个参数
命名参数【官推】
官方推荐!
XXDao.java
:
public List<Student> selectMultiParam(@Param("name") String name, @Param("age") Integer age);
XXDao.xml
:
<select resultType="cn.ethy.entity.Students">
SELECT * FROM students WHERE name = #{name} AND age = #{age};
</select>
使用对象【掌握】
创建一个参数类,如 QueryParam.java
:
public class QueryParam {
private String paramName;
private String paramAge;
}
XXDao.xml
:
<select resultType="cn.ethy.entity.Students">
SELECT * FROM students WHERE name= #{paramName} AND age= #{paramAge};
</select>
完整形式:
#{属性名, javaType=类型名称, jdbcType=数据类型}
如:
#{paramAge, javaType=java.lang.Integer, jdbcType=INTEGER}
后两者 mybatis 可以通过反射获取,所以无需提供
或者也可以直接用Student对象,就如上面的一个例子
按位置【了解】
mybatis-3.3 版本和之前的版本使用#{0},#{1}方式
从 mybatis3.4 开始使用#方式
Map【了解】
Map<String,Object> data = new HashMap<String,Object>();
data.put(“myname”,”李力”);
data.put(“myage”,20);
<select id="selectMultiMap" resultType="cn.ethy.entity.Student">
select id,name,email,age from student where name=#{myname} or age =#{myage}
</select>
$和#
$
使用的是 Statement,有安全注入的问题
#
使用的是 PrepareStatement,预编译
如果你能确定参数是安全的,则可以用前者,比如代替列名等(不是用户输入)
模糊查询
方式一:
在Java代码中提供:
@Test
public void testSelectLikeOne(){
String name="%力%";
List<Student> stuList = studentDao.selectLikeFirst(name);
stuList.forEach( stu -> System.out.println(stu));
}
<select id="selectLikeFirst" resultType="com.bjpowernode.domain.Student">
select id,name,email,age from student
where name like #{studentName}
</select>
方式二:
mapper文件中使用,类似 like name "%" #{XXX} "%"
的形式
注意其中的空格
@Test
public void testSelectLikeSecond(){
String name="力";
List<Student> stuList = studentDao.selectLikeSecond(name);
stuList.forEach( stu -> System.out.println(stu));
}
<select id="selectLikeSecond" resultType="com.bjpowernode.domain.Student">
select id,name,email,age from student
where name like "%" #{studentName} "%"
</select>
返回结果
resultType
: 执行 sql 得到 ResultSet 转换的类型,使用类型的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。resultType 和 resultMap 不能同时使用。
简单类型
同上面 parameterType
的叙述
对象类型
同上面 parameterType
的叙述
Map类型
案例:
Map<Object, Object> selectReturnMap(int id);
<select id="selectReturnMap" resultType="java.util.HashMap">
select name,email from student where id = #{studentId}
</select>
Map 作为接口返回值,sql 语句的查询结果最多只能有一条记录。大于一条记录是错误。
resultMap
resultMap 可以自定义 sql 的结果和 java 对象属性的映射关系。更灵活的把列值赋值给指定属性。常用在列名和 java 对象属性名不一样的情况。
<resultMap id="studentMap" type="com.bjpowernode.domain.Student">
<!-- 主键字段使用 id -->
<id column="id" property="id" />
<!--非主键字段使用 result-->
<result column="name" property="name"/>
<result column="email" property="email" />
<result column="age" property="age" />
</resultMap>
<!--resultMap: resultMap 标签中的 id 属性值--> <select id="selectUseResultMap" resultMap="studentMap">
select id,name,email,age from student where name=#{queryName} or
age=#{queryAge}
</select>
如上所示,还可以多次重复利用。
类属性名和列名不同
有两种处理方法:
- 使用上面提到的
resultMap
方法进行处理 - 在 SQL 语句中起别名,如
select name as stuName ...
之类的
动态SQL
动态 SQL,主要用于解决查询条件不确定的情况:在程序运行期间,根据用户提交的查询条件进行查询。提交的查询条件不同,执行的 SQL 语句不同。若将每种可能的情况均逐一列出,对所有条件进行排列组合,将会出现大量的 SQL 语句。此时,可使用动态 SQL 来解决这样的问题。也可以减少字符串拼接的复杂操作
XML 中部分特殊字符应该转义:
原来 | 转义后 |
---|---|
< | < |
> | > |
>= | >= |
<= | <= |
if
<select id="selectStudentIf" resultType="com.bjpowernode.domain.Student">
select id,name,email,age from student
where 1=1
<if test="name != null and name !='' ">
and name = #{name}
</if>
<if test="age > 0 ">
and age > #{age}
</if>
</select>
为什么要 1 = 1 ? 因为如果没有这个语句,且下面的某个 if
块不成立,SQL语句会有多余的 AND,造成 SQL 语法错误。
也可以用下面的 where 来解决这个问题:
where
<if/>
标签的中存在一个比较麻烦的地方:需要在 where 后手工添加 1=1 的子句。因为,若 where 后的所有 <if/>
条件均为 false,而 where 后若又没有 1=1 子句,则 SQL 中就会只剩下一个空的 where,SQL出错。所以,在 where 后,需要添加永为真子句 1=1,以防止这种情况的发生。但当数据量很大时,会严重影响查询效率。
使用 <where/>
标签,在有查询条件时,可以自动添加上 where 子句;没有查询条件时,不会添加where 子句。需要注意的是,第一个 <if/>
标签中的 SQL 片断,可以不包含 and。不过,写上 and 也不错,系统会将多出的 and 去掉。但其它 <if/>
中 SQL 片断的 and,必须要求写上。否则 SQL 语句将拼接出错。
<select id="selectStudentWhere" resultType="com.bjpowernode.domain.Student">
select id,name,email,age from student
<where>
<if test="name != null and name !='' ">
and name = #{name}
</if>
<if test="age > 0 ">
and age > #{age}
</if>
</where>
</select>
foreach
<foreach/>
标签用于实现对于数组与集合的遍历。对其使用,需要注意:
- collection 表示要遍历的集合类型, list ,array 等。
- open、close、separator 为对遍历内容的 SQL 拼接。
<foreach collection="集合类型" open="开始的字符" close="结束的字符"
item="集合中的成员" separator="集合成员之间的分隔符">
#{item 的值}
</foreach>
案例:
<select id="selectStudentForList2"
resultType="com.bjpowernode.domain.Student">
select id,name,email,age from student
<if test="list !=null and list.size > 0 ">
where id in
<foreach collection="list" open="(" close=")"
item="stuobject" separator=",">
#{stuobject.id}
</foreach>
</if>
</select>
代码片段
定义 SQL 片段,以方便代码复用
<!--创建 sql 片段 id:片段的自定义名称-->
<sql id="studentSql">
select id,name,email,age from student
</sql>
<select id="selectStudentSqlFragment" resultType="com.bjpowernode.domain.Student">
<!-- 引用 sql 片段 -->
<include refid="studentSql"/>
<if test="list !=null and list.size > 0 ">
where id in
<foreach collection="list" open="(" close=")"
item="stuobject" separator=",">
#{stuobject.id}
</foreach>
</if>
</select>
配置补充
外部属性配置文件
resources/jdbc.properties
:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?charset=UTF-8
jdbc.username=root
jdbc.password=123456
修改 mybatis.xml
:
<properties resource="jdbc.properties"></properties>
<dataSource type="POOLED">
<!--使用 properties 文件: 语法 ${key}-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
类型别名
<!--
定义单个类型的别名
type:类型的全限定名称
alias:自定义别名
-->
北京动力节点 www.bjpowernode.com
北京动力节点 www.bjpowernode.com
<typeAlias type="com.bjpowernode.domain.Student" alias="mystudent"/>
<!--
批量定义别名,扫描整个包下的类,别名为类名(首字母大写或小写都可以)
name:包名
-->
<package name="com.bjpowernode.domain"/>
<package name="...其他包"/>
</typeAliases>
批量映射
之前这样一次只能映射一个 Mapper 文件:
<mappers>
<!--一个mapper标签指定一个文件的位置-->
<!--从类路径开始的路径信息-->
<mapper resource="cn/ethy/dao/StudentDao.xml"/>
</mappers>
这样可以映射一个包:
<mappers>
<package name="cn.ethy.dao"/>
</mappers>
评论区