MyBatis
基本介绍
ORM(Object Relational Mapping): 对象关系映射,指的是持久化数据和实体对象的映射模式,解决面向对象与关系型数据库存在的互不匹配的现象
MyBatis:
-
MyBatis 是一个优秀的基于 Java 的持久层框架,它内部封装了 JDBC,使开发者只需关注 SQL 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 Statement 等过程。
-
MyBatis 通过 XML 或注解的方式将要执行的各种 Statement 配置起来,并通过 Java 对象和 Statement 中 SQL 的动态参数进行映射生成最终执行的 SQL 语句。
-
MyBatis 框架执行 SQL 并将结果映射为 Java 对象并返回。采用 ORM 思想解决了实体和数据库映射的问题,对 JDBC 进行了封装,屏蔽了 JDBC 底层 API 的调用细节,使我们不用操作 JDBC API,就可以完成对数据库的持久化操作。
MyBatis 官网地址:http://www.mybatis.org/mybatis-3/
参考视频:https://space.bilibili.com/37974444/
基本操作
相关API
Resources:加载资源的工具类
InputStream getResourceAsStream(String fileName)
:通过类加载器返回指定资源的字节流- 参数 fileName 是放在 src 的核心配置文件名:MyBatisConfig.xml
SqlSessionFactoryBuilder:构建器,用来获取 SqlSessionFactory 工厂对象
SqlSessionFactory build(InputStream is)
:通过指定资源的字节输入流获取 SqlSession 工厂对象
SqlSessionFactory:获取 SqlSession 构建者对象的工厂接口
SqlSession openSession()
:获取 SqlSession 构建者对象,并开启手动提交事务SqlSession openSession(boolean)
:获取 SqlSession 构建者对象,参数为 true 开启自动提交事务
SqlSession:构建者对象接口,用于执行 SQL、管理事务、接口代理
- SqlSession 代表和数据库的一次会话,用完必须关闭
- SqlSession 和 Connection 一样都是非线程安全,每次使用都应该去获取新的对象
注:update 数据需要提交事务,或开启默认提交
SqlSession 常用 API:
方法 | 说明 |
---|---|
List | 执行查询语句,返回List集合 |
T selectOne(String statement,Object parameter) | 执行查询语句,返回一个结果对象 |
int insert(String statement,Object parameter) | 执行新增语句,返回影响行数 |
int update(String statement,Object parameter) | 执行删除语句,返回影响行数 |
int delete(String statement,Object parameter) | 执行修改语句,返回影响行数 |
void commit() | 提交事务 |
void rollback() | 回滚事务 |
T getMapper(Class | 获取指定接口的代理实现类对象 |
void close() | 释放资源 |
映射配置
映射配置文件包含了数据和对象之间的映射关系以及要执行的 SQL 语句,放在 src 目录下
命名:StudentMapper.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"> -
根标签:
:核心根标签 - namespace:属性,名称空间
-
功能标签:
- < select >:查询功能标签
:新增功能标签 :修改功能标签 :删除功能标签 - id:属性,唯一标识,配合名称空间使用
- resultType:指定结果映射对象类型,和对应的方法的返回值类型(全限定名)保持一致,但是如果返回值是 List 则和其泛型保持一致
- parameterType:指定参数映射对象类型,必须和对应的方法的参数类型(全限定名)保持一致
- statementType:可选 STATEMENT,PREPARED 或 CALLABLE,默认值:PREPARED
- STATEMENT:直接操作 SQL,使用 Statement 不进行预编译,获取数据:$
- PREPARED:预处理参数,使用 PreparedStatement 进行预编译,获取数据:#
- CALLABLE:执行存储过程,CallableStatement
-
参数获取方式:
-
SQL 获取参数:
#{属性名}
<mapper namespace="StudentMapper">
<select id="selectById" resultType="student" parameterType="int">
SELECT * FROM student WHERE id = #{id}
</select>
<mapper/>
-
强烈推荐官方文档:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html
核心配置
核心配置文件包含了 MyBatis 最核心的设置和属性信息,如数据库的连接、事务、连接池信息等
命名:MyBatisConfig.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"> -
根标签:
:核心根标签
-
引入连接配置文件:
-
: 引入数据库连接配置文件标签 - resource:属性,指定配置文件名
<properties resource="jdbc.properties"/>
-
-
调整设置
:可以改变 Mybatis 运行时行为
-
起别名:
-
:为全类名起别名的父标签 :为全类名起别名的子标签 - type:指定全类名
- alias:指定别名
:为指定包下所有类起别名的子标签,别名就是类名,首字母小写
<!--起别名-->
<typeAliases>
<typeAlias type="bean.Student" alias="student"/>
<package name="com.seazean.bean"/>
<!--二选一-->
</typeAliase> -
自带别名:
别名 数据类型 string java.lang.String long java.lang.Lang int java.lang.Integer double java.lang.Double boolean java.lang.Boolean .... ......
-
-
配置环境,可以配置多个标签
:配置数据库环境标签,default 属性指定哪个 environment :配置数据库环境子标签,id 属性是唯一标识,与 default 对应 :事务管理标签,type 属性默认 JDBC 事务 :数据源标签 - type 属性:POOLED 使用连接池(MyBatis 内置),UNPOOLED 不使用连接池
:数据库连接信息标签。 - name 属性取值:driver,url,username,password
- value 属性取值:与 name 对应
-
引入映射配置文件
:引入映射配置文件标签 :引入映射配置文件子标签 - resource:属性指定映射配置文件的名称
- url:引用网路路径或者磁盘路径下的 sql 映射文件
- class:指定映射配置类
:批量注册
参考官方文档:https://mybatis.org/mybatis-3/zh/configuration.html
#{}和${}
#{}:占位符,传入的内容会作为字符串加上引号,以预编译的方式传入,将 sql 中的 #{} 替换为 ? 号,调用 PreparedStatement 的 set 方法来赋值,有效的防止 SQL 注入,提高系统安全性
${}:拼接符,传入的内容会直接替换拼接,不会加上引号,可能存在 sql 注入的安全隐患
-
能用 #{} 的地方就用 #{},不用或少用 ${}
-
必须使用 ${} 的情况:
- 表名作参数时,如:
SELECT * FROM ${tableName}
- order by 时,如:
SELECT * FROM t_user ORDER BY ${columnName}
- 表名作参数时,如:
-
sql 语句使用 #{},properties 文件内容获取使用 ${}
日志文件
在日常开发过程中,排查问题时需要输出 MyBatis 真正执行的 SQL 语句、参数、结果等信息,就可以借助 log4j 的功能来实现执行信息的输出。
-
在核心配置文件根标签内配置 log4j
<!--配置LOG4J-->
<settings>
<setting name="logImpl" value="log4j"/>
</settings> -
在 src 目录下创建 log4j.properties
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
#输出到日志文件
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=../logs/iask.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %m%n -
pom.xml
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
代码实现
-
实体类
public class Student {
private Integer id;
private String name;
private Integer age;
.....
} -
StudentMapper
public interface StudentMapper {
//查询全部
public abstract List<Student> selectAll();
//根据id查询
public abstract Student selectById(Integer id);
//新增数据
public abstract Integer insert(Student stu);
//修改数据
public abstract Integer update(Student stu);
//删除数据
public abstract Integer delete(Integer id);
} -
config.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.2.184:3306/db1
username=root
password=123456 -
MyBatisConfig.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="jdbc.properties"/>
<!--配置LOG4J-->
<settings>
<setting name="logImpl" value="log4j"/>
</settings>
<!--起别名-->
<typeAliases>
<typeAlias type="bean.Student" alias="student"/>
<!--<package name="bean"/>-->
</typeAliases>
<!--配置数据库环境,可以多个环境,default指定哪个-->
<environments default="mysql">
<!--id属性唯一标识-->
<environment id="mysql">
<!--事务管理,type属性,默认JDBC事务-->
<transactionManager type="JDBC"></transactionManager>
<!--数据源信息 type属性连接池-->
<dataSource type="POOLED">
<!--property获取数据库连接的配置信息-->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--引入映射配置文件-->
<mappers>
<!--mapper引入指定的映射配置 resource属性执行的映射配置文件的名称-->
<mapper resource="StudentMapper.xml"/>
</mappers>
</configuration> -
StudentMapper.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="StudentMapper">
<select id="selectAll" resultType="student">
SELECT * FROM student
</select>
<select id="selectById" resultType="student" parameterType="int">
SELECT * FROM student WHERE id = #{id}
</select>
<insert id="insert" parameterType="student">
INSERT INTO student VALUES (#{id},#{name},#{age})
</insert>
<update id="update" parameterType="student">
UPDATE student SET name = #{name}, age = #{age} WHERE id = #{id}
</update>
<delete id="delete" parameterType="student">
DELETE FROM student WHERE id = #{id}
</delete>
</mapper> -
控制层测试代码:根据 id 查询
@Test
public void selectById() throws Exception{
//1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = ssf.openSession();
//4.执行映射配置文件中的sql语句,并接收结果
Student stu = sqlSession.selectOne("StudentMapper.selectById", 3);
//5.处理结果
System.out.println(stu);
//6.释放资源
sqlSession.close();
is.close();
} -
控制层测试代码:新增功能
@Test
public void insert() throws Exception{
//1.加载核心配置文件
//2.获取SqlSession工厂对象
//3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//4.执行映射配置文件中的sql语句,并接收结果
Student stu = new Student(5, "周七", 27);
int result = sqlSession.insert("StudentMapper.insert", stu);
//5.提交事务
//sqlSession.commit();
//6.处理结果
System.out.println(result);
//7.释放资源
sqlSession.close();
is.close();
}
批量操作
三种方式实现批量操作:
-
标签属性:这种方式属于全局批量 <settings>
<setting name="defaultExecutorType" value="BATCH"/>
</settings>defaultExecutorType:配置默认的执行器
- SIMPLE 就是普通的执行器(默认,每次执行都要重新设置参数)
- REUSE 执行器会重用预处理语句(只预设置一次参数,多次执行)
- BATCH 执行器不仅重用语句还会执行批量更新(只针对修改操作)
-
SqlSession 会话内批量操作:
public void testBatch() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 可以执行批量操作的sqlSession
SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
long start = System.currentTimeMillis();
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
for (int i = 0; i < 10000; i++) {
mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
}
openSession.commit();
long end = System.currentTimeMillis();
// 批量:(预编译sql一次==>设置参数===>10000次===>执行1次(类似管道))
// 非批量:(预编译sql=设置参数=执行)==》10000 耗时更多
System.out.println("执行时长:" + (end - start));
}finally{
openSession.close();
}
} -
Spring 配置文件方式(applicationContext.xml):
<!--配置一个可以进行批量执行的sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>
<constructor-arg name="executorType" value="BATCH"/>
</bean>@Autowired
private SqlSession sqlSession;
代理开发
代理规则
分层思想:控制层(controller)、业务层(service)、持久层(dao)
调用流程:
传统方式实现 DAO 层,需要写接口和实现类。采用 Mybatis 的代理开发方式实现 DAO 层的开发,只需要编写 Mapper 接口(相当于 Dao 接口),由 Mybatis 框架根据接口定义创建接口的动态代理对象
接口开发方式:
- 定义接口
- 操作数据库,MyBatis 框架根据接口,通过动态代理的方式生成代理对象,负责数据库的操作
Mapper 接口开发需要遵循以下规范:
-
Mapper.xml 文件中的 namespace 与 DAO 层 mapper 接口的全类名相同
-
Mapper.xml 文件中的增删改查标签的id属性和 DAO 层 Mapper 接口方法名相同
-
Mapper.xml 文件中的增删改查标签的 parameterType 属性和 DAO 层 Mapper 接口方法的参数相同
-
Mapper.xml 文件中的增删改查标签的 resultType 属性和 DAO 层 Mapper 接口方法的返回值相同
实现原理
通过动态代理开发模式,只编写一个接口不写实现类,通过 getMapper() 方法最终获取到 MapperProxy 代理对象,而这个代理对象是 MyBatis 使用了 JDK 的动态代理技术生成的
动态代理实现类对象在执行方法时最终调用了 MapperMethod.execute() 方法,这个方法中通过 switch case 语句根据操作类型来判断是新增、修改、删除、查询操作,最后一步回到了 MyBatis 最原生的 SqlSession 方式来执行增删改查
-
代码实现:
public Student selectById(Integer id) {
Student stu = null;
SqlSession sqlSession = null;
InputStream is = null;
try{
//1.加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory s = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
sqlSession = s.openSession(true);
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//5.通过实现类对象调用方法,接收结果
stu = mapper.selectById(id);
} catch (Exception e) {
e.getMessage();
} finally {
//6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//7.返回结果
return stu;
}
结果映射
相关标签
- id 属性:唯一标识
- type 属性:实体对象类型
- autoMapping 属性:结果自动映射
-
:配置主键映射关系标签 -
:配置非主键映射关系标签 - column 属性:表中字段名称
- property 属性: 实体对象变量名称
-
:配置被包含单个对象的映射关系标签,嵌套封装结果集(多对一、一对一) - property 属性:被包含对象的变量名,要进行映射的属性名
- javaType 属性:被包含对象的数据类型,要进行映射的属性的类型(Java 中的 Bean 类)
- select 属性:加载复杂类型属性的映射语句的 ID,会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句
-
:配置被包含集合对象的映射关系标签,嵌套封装结果集(一对多、多对多) - property 属性:被包含集合对象的变量名
- ofType 属性:集合中保存的对象数据类型
-
:鉴别器,用来判断某列的值,根据得到某列的不同值做出不同自定义的封装行为
自定义封装规则可以将数据库中比较复杂的数据类型映射为 JavaBean 中的属性
嵌套查询
子查询:
public class Blog {
private int id;
private String msg;
private Author author;
// set + get
}
<resultMap id="blogResult" type="Blog" autoMapping = "true">
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectAuthor" resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
循环引用:通过缓存解决
<resultMap id="blogResult" type="Blog" autoMapping = "true">
<id column="id" property="id"/>
<collection property="comment" ofType="Comment">
<association property="blog" javaType="Blog" resultMap="blogResult"/><!--y-->
</collection>
</resultMap
多表查询
一对一
一对一实现:
-
数据准备
CREATE TABLE person(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
age INT
);
INSERT INTO person VALUES (NULL,'张三',23),(NULL,'李四',24),(NULL,'王五',25);
CREATE TABLE card(
id INT PRIMARY KEY AUTO_INCREMENT,
number VARCHAR(30),
pid INT,
CONSTRAINT cp_fk FOREIGN KEY (pid) REFERENCES person(id)
);
INSERT INTO card VALUES (NULL,'12345',1),(NULL,'23456',2),(NULL,'34567',3); -
bean 类
public class Card {
private Integer id; //主键id
private String number; //身份证号
private Person p; //所属人的对象
......
}
public class Person {
private Integer id; //主键id
private String name; //人的姓名
private Integer age; //人的年龄
} -
配置文件 OneToOneMapper.xml,MyBatisConfig.xml 需要引入(可以把 bean 包下起别名)
<?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="OneToOneMapper">
<!--配置字段和实体对象属性的映射关系-->
<resultMap id="oneToOne" type="card">
<!--column 表中字段名称,property 实体对象变量名称-->
<id column="cid" property="id" />
<result column="number" property="number" />
<!--
association:配置被包含对象的映射关系
property:被包含对象的变量名
javaType:被包含对象的数据类型
-->
<association property="p" javaType="bean.Person">
<id column="pid" property="id" />
<result column="name" property="name" />
<result column="age" property="age" />
</association>
</resultMap>
<select id="selectAll" resultMap="oneToOne"> <!--SQL-->
SELECT c.id cid,number,pid,NAME,age FROM card c,person p WHERE c.pid=p.id
</select>
</mapper> -
核心配置文件 MyBatisConfig.xml
<!-- mappers引入映射配置文件 -->
<mappers>
<mapper resource="one_to_one/OneToOneMapper.xml"/>
<mapper resource="one_to_many/OneToManyMapper.xml"/>
<mapper resource="many_to_many/ManyToManyMapper.xml"/>
</mappers> -
测试类
public class Test01 {
@Test
public void selectAll() throws Exception{
//1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = ssf.openSession(true);
//4.获取OneToOneMapper接口的实现类对象
OneToOneMapper mapper = sqlSession.getMapper(OneToOneMapper.class);
//5.调用实现类的方法,接收结果
List<Card> list = mapper.selectAll();
//6.处理结果
for (Card c : list) {
System.out.println(c);
}
//7.释放资源
sqlSession.close();
is.close();
}
}
一对多
一对多实现:
-
数据准备
CREATE TABLE classes(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20)
);
INSERT INTO classes VALUES (NULL,'程序一班'),(NULL,'程序二班')
CREATE TABLE student(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(30),
age INT,
cid INT,
CONSTRAINT cs_fk FOREIGN KEY (cid) REFERENCES classes(id)
);
INSERT INTO student VALUES (NULL,'张三',23,1),(NULL,'李四',24,1),(NULL,'王五',25,2); -
bean 类
public class Classes {
private Integer id; //主键id
private String name; //班级名称
private List<Student> students; //班级中所有学生对象
........
}
public class Student {
private Integer id; //主键id
private String name; //学生姓名
private Integer age; //学生年龄
} -
映射配置文件
<mapper namespace="OneToManyMapper">
<resultMap id="oneToMany" type="bean.Classes">
<id column="cid" property="id"/>
<result column="cname" property="name"/>
<!--collection:配置被包含的集合对象映射关系-->
<collection property="students" ofType="bean.Student">
<id column="sid" property="id"/>
<result column="sname" property="name"/>
<result column="sage" property="age"/>
</collection>
</resultMap>
<select id="selectAll" resultMap="oneToMany"> <!--SQL-->
SELECT c.id cid,c.name cname,s.id sid,s.name sname,s.age sage FROM classes c,student s WHERE c.id=s.cid
</select>
</mapper> -
代码实现片段
//4.获取OneToManyMapper接口的实现类对象
OneToManyMapper mapper = sqlSession.getMapper(OneToManyMapper.class);
//5.调用实现类的方法,接收结果
List<Classes> classes = mapper.selectAll();
//6.处理结果
for (Classes cls : classes) {
System.out.println(cls.getId() + "," + cls.getName());
List<Student> students = cls.getStudents();
for (Student student : students) {
System.out.println("\t" + student);
}
}
多对多
学生课程例子,中间表不需要 bean 实体类
-
数据准备
CREATE TABLE course(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20)
);
INSERT INTO course VALUES (NULL,'语 文'),(NULL,'数学');
CREATE TABLE stu_cr(
id INT PRIMARY KEY AUTO_INCREMENT,
sid INT,
cid INT,
CONSTRAINT sc_fk1 FOREIGN KEY (sid) REFERENCES student(id),
CONSTRAINT sc_fk2 FOREIGN KEY (cid) REFERENCES course(id)
);
INSERT INTO stu_cr VALUES (NULL,1,1),(NULL,1,2),(NULL,2,1),(NULL,2,2); -
bean类
public class Student {
private Integer id; //主键id
private String name; //学生姓名
private Integer age; //学生年龄
private List<Course> courses; // 学生所选择的课程集合
}
public class Course {
private Integer id; //主键id
private String name; //课程名称
} -
配置文件
<mapper namespace="ManyToManyMapper">
<resultMap id="manyToMany" type="Bean.Student">
<id column="sid" property="id"/>
<result column="sname" property="name"/>
<result column="sage" property="age"/>
<collection property="courses" ofType="Bean.Course">
<id column="cid" property="id"/>
<result column="cname" property="name"/>
</collection>
</resultMap>
<select id="selectAll" resultMap="manyToMany"> <!--SQL-->
SELECT sc.sid,s.name sname,s.age sage,sc.cid,c.name cname FROM student s,course c,stu_cr sc WHERE sc.sid=s.id AND sc.cid=c.id
</select>
</mapper>
鉴别器
需求:如果查询结果是女性,则把部门信息查询出来,否则不查询 ;如果是男性,把 last_name 这一列的值赋值
<!--
column:指定要判断的列名
javaType:列值对应的java类型
-->
<discriminator javaType="string" column="gender">
<!-- 女生 -->
<!-- resultType不可缺少,也可以使用resutlMap -->
<case value="0" resultType="com.bean.Employee">
<association property="dept"
select="com.dao.DepartmentMapper.getDeptById"
column="d_id">
</association>
</case>
<!-- 男生 -->
<case value="1" resultType="com.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
</case>
</discriminator>
延迟加载
两种加载
立即加载:只要调用方法,马上发起查询
延迟加载:在需要用到数据时才进行加载,不需要用到数据时就不加载数据,延迟加载也称懒加载
优点: 先从单表查询,需要时再从关联表去关联查询,提高数据库性能,因为查询单表要比关联查询多张表速度要快,节省资源
坏处:只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,查询工作也要消耗时间,所以可能造成用户等 待时间变长,造成用户体验下降
核心配置文件:
标签名 | 描述 | 默认值 |
---|---|---|
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载,特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | false |
aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。否则每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods) | false |
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
assocation
分布查询:先按照身份 id 查询所属人的 id、然后根据所属人的 id 去查询人的全部信息,这就是分步查询
-
映射配置文件 OneToOneMapper.xml
一对一映射:
- column 属性表示给要调用的其它的 select 标签传入的参数
- select 属性表示调用其它的 select 标签
- fetchType="lazy" 表示延迟加载(局部配置,只有配置了这个的地方才会延迟加载)
<mapper namespace="OneToOneMapper">
<!--配置字段和实体对象属性的映射关系-->
<resultMap id="oneToOne" type="card">
<id column="id" property="id" />
<result column="number" property="number" />
<association property="p" javaType="bean.Person"
column="pid"
select="one_to_one.PersonMapper.findPersonByid"
fetchType="lazy">
<!--需要配置新的映射文件-->
</association>
</resultMap>
<select id="selectAll" resultMap="oneToOne">
SELECT * FROM card <!--查询全部,负责根据条件直接全部加载-->
</select>
</mapper> -
PersonMapper.xml
<mapper namespace="one_to_one.PersonMapper">
<select id="findPersonByid" parameterType="int" resultType="person">
SELECT * FROM person WHERE id=#{pid}
</select>
</mapper> -
PersonMapper.java
public interface PersonMapper {
User findPersonByid(int id);
} -
测试文件
public class Test01 {
@Test
public void selectAll() throws Exception{
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = ssf.openSession(true);
OneToOneMapper mapper = sqlSession.getMapper(OneToOneMapper.class);
// 调用实现类的方法,接收结果
List<Card> list = mapper.selectAll();
// 不能遍历,遍历就是相当于使用了该数据,需要加载,不遍历就是没有使用。
// 释放资源
sqlSession.close();
is.close();
}
}
collection
同样在一对多关系配置的
-
映射配置文件 OneToManyMapper.xml
一对多映射:
- column 是用于指定使用哪个字段的值作为条件查询
- select 是用于指定查询账户的唯一标识(账户的 dao 全限定类名加上方法名称)
<mapper namespace="OneToManyMapper">
<resultMap id="oneToMany" type="bean.Classes">
<id column="id" property="id"/>
<result column="name" property="name"/>
<!--collection:配置被包含的集合对象映射关系-->
<collection property="students" ofType="bean.Student"
column="id"
select="one_to_one.StudentMapper.findStudentByCid">
</collection>
</resultMap>
<select id="selectAll" resultMap="oneToMany">
SELECT * FROM classes
</select>
</mapper> -
StudentMapper.xml
<mapper namespace="one_to_one.StudentMapper">
<select id="findPersonByCid" parameterType="int" resultType="student">
SELECT * FROM person WHERE cid=#{id}
</select>
</mapper>
注解开发
单表操作
注解可以简化开发操作,省略映射配置文件的编写
常用注解:
- @Select(“查询的 SQL 语句”):执行查询操作注解
- @Insert(“插入的 SQL 语句”):执行新增操作注解
- @Update(“修改的 SQL 语句”):执行修改操作注解
- @Delete(“删除的 SQL 语句”):执行删除操作注解
参数注解:
- @Param:当 SQL 语句需要多个(大于1)参数时,用来指定参数的对应规则
核心配置文件配置映射关系:
<mappers>
<package name="使用了注解的Mapper接口所在包"/>
</mappers>
<!--或者-->
<mappers>
<mapper class="包名.Mapper名"></mapper>
</mappers>
基本增删改查:
-
创建 Mapper 接口
package mapper;
public interface StudentMapper {
//查询全部
@Select("SELECT * FROM student")
public abstract List<Student> selectAll();
//新增数据
@Insert("INSERT INTO student VALUES (#{id},#{name},#{age})")
public abstract Integer insert(Student student);
//修改操作
@Update("UPDATE student SET name=#{name},age=#{age} WHERE id=#{id}")
public abstract Integer update(Student student);
//删除操作
@Delete("DELETE FROM student WHERE id=#{id}")
public abstract Integer delete(Integer id);
} -
修改 MyBatis 的核心配置文件
<mappers>
<package name="mapper"/>
</mappers> -
bean类
public class Student {
private Integer id;
private String name;
private Integer age;
} -
测试类
@Test
public void selectAll() throws Exception{
//1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = ssf.openSession(true);
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//5.调用实现类对象中的方法,接收结果
List<Student> list = mapper.selectAll();
//6.处理结果
for (Student student : list) {
System.out.println(student);
}
//7.释放资源
sqlSession.close();
is.close();
}
多表操作
相关注解
实现复杂关系映射之前我们可以在映射文件中通过配置
注解 | 说明 |
---|---|
@Results | 代替 使用格式:@Results({ @Result(), @Result() })或@Results({ @Result() }) |
@Result | 代替< id> 和 column:数据库的列名 property:封装类的变量名 one:需要使用 @One 注解(@Result(one = @One)) Many:需要使用 @Many 注解(@Result(many= @Many)) |
@One(一对一) | 代替 select:指定调用 Mapper 接口中的某个方法 使用格式:@Result(column="", property="", one=@One(select="")) |
@Many(多对一) | 代替 select:指定调用 Mapper 接口中的某个方法 使用格式:@Result(column="", property="", many=@Many(select="")) |
一对一
身份证对人
-
PersonMapper 接口
public interface PersonMapper {
//根据id查询
@Select("SELECT * FROM person WHERE id=#{id}")
public abstract Person selectById(Integer id);
} -
CardMapper接口
public interface CardMapper {
//查询全部
@Select("SELECT * FROM card")
@Results({
@Result(column = "id",property = "id"),
@Result(column = "number",property = "number"),
@Result(
property = "p", // 被包含对象的变量名
javaType = Person.class, // 被包含对象的实际数据类型
column = "pid", // 根据查询出的card表中的pid字段来查询person表
/*
one、@One 一对一固定写法
select属性:指定调用哪个接口中的哪个方法
*/
one = @One(select = "one_to_one.PersonMapper.selectById")
)
})
public abstract List<Card> selectAll();
} -
测试类(详细代码参考单表操作)
//1.加载核心配置文件
//2.获取SqlSession工厂对象
//3.通过工厂对象获取SqlSession对象
//4.获取StudentMapper接口的实现类对象
CardMapper mapper = sqlSession.getMapper(CardMapper.class);
//5.调用实现类对象中的方法,接收结果
List<Card> list = mapper.selectAll();
一对多
班级和学生
-
StudentMapper接口
public interface StudentMapper {
//根据cid查询student表 cid是外键约束列
@Select("SELECT * FROM student WHERE cid=#{cid}")
public abstract List<Student> selectByCid(Integer cid);
} -
ClassesMapper接口
public interface ClassesMapper {
//查询全部
@Select("SELECT * FROM classes")
@Results({
@Result(column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(
property = "students", //被包含对象的变量名
javaType = List.class, //被包含对象的实际数据类型
column = "id", //根据id字段查询student表
many = @Many(select = "one_to_many.StudentMapper.selectByCid")
)
})
public abstract List<Classes> selectAll();
} -
测试类
//4.获取StudentMapper接口的实现类对象
ClassesMapper mapper = sqlSession.getMapper(ClassesMapper.class);
//5.调用实现类对象中的方法,接收结果
List<Classes> classes = mapper.selectAll();
多对多
学生和课程
-
SQL 查询语句
SELECT DISTINCT s.id,s.name,s.age FROM student s,stu_cr sc WHERE sc.sid=s.id
SELECT c.id,c.name FROM stu_cr sc,course c WHERE sc.cid=c.id AND sc.sid=#{id} -
CourseMapper 接口
public interface CourseMapper {
//根据学生id查询所选课程
@Select("SELECT c.id,c.name FROM stu_cr sc,course c WHERE sc.cid=c.id AND sc.sid=#{id}")
public abstract List<Course> selectBySid(Integer id);
} -
StudentMapper 接口
public interface StudentMapper {
//查询全部
@Select("SELECT DISTINCT s.id,s.name,s.age FROM student s,stu_cr sc WHERE sc.sid=s.id")
@Results({
@Result(column = "id",property = "id"),
@Result(column = "name",property = "name"),
@Result(column = "age",property = "age"),
@Result(
property = "courses", //被包含对象的变量名
javaType = List.class, //被包含对象的实际数据类型
column = "id", //根据查询出的student表 中的id字段查询中间表和课程表
many = @Many(select = "many_to_many.CourseMapper.selectBySid")
)
})
public abstract List<Student> selectAll();
} -
测试类
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//5.调用实现类对象中的方法,接收结果
List<Student> students = mapper.selectAll();
缓存机制
缓存概述
缓存:缓存就是一块内存空间,保存临时数据
作用:将数据源(数据库或者文件)中的数据读取出来存放到缓存中,再次获取时直接从缓存中获取,可以减少和数据库交互的次数,提升程序的性能
缓存适用:
- 适用于缓存的:经常查询但不经常修改的,数据的正确与否对最终结果影响不大的
- 不适用缓存的:经常改变的数据 , 敏感数据(例如:股市的牌价,银行的汇率,银行卡里面的钱)等等
缓 存类别:
- 一级缓存:SqlSession 级别的缓存,又叫本地会话缓存,自带的(不需要配置),一级缓存的生命周期与 SqlSession 一致。在操作数据库时需要构造 SqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据,不同的 SqlSession 之间的缓存数据区域是互相不影响的
- 二级缓存:mapper(namespace)级别的缓存,二级缓存的使用,需要手动开启(需要配置)。多个 SqlSession 去操作同一个 Mapper 的 SQL 可以共用二级缓存,二级缓存是跨 SqlSession 的
开启缓存:配置核心配置文件中
- cacheEnabled:true 表示全局性地开启所有映射器配置文件中已配置的任何缓存,默认 true
参考文章:https://www.cnblogs.com/ysocean/p/7342498.html
一级缓存
一级缓存是 SqlSession 级别的缓存
工作流程:第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息,得到用户信息,将用户信息存储到一级缓存中;第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
一级缓存的失效:
- SqlSession 不同
- SqlSession 相同,查询条件不同时(还未缓存该数据)
- SqlSession 相同,手动清除了一级缓存,调用
sqlSession.clearCache()
- SqlSession 相同,执行 commit 操作或者执行插入、更新、删除,清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读
Spring 整合 MyBatis 后,一级缓存作用:
- 未开启事务的情况,每次查询 Spring 都会创建新的 SqlSession,因此一级缓存失效
- 开启事务的情况,Spring 使用 ThreadLocal 获取当前资源绑定同一个 SqlSession,因此此时一级缓存是有效的
测试一级缓存存在
public void testFirstLevelCache(){
//1. 获取sqlSession对象
SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
//2. 通过sqlSession对象获取UserDao接口的代理对象
UserDao userDao1 = sqlSession.getMapper(UserDao.class);
//3. 调用UserDao接口的代理对象的findById方法获取信息
User user1 = userDao1.findById(1);
System.out.println(user1);
//sqlSession.clearCache() 清空缓存
UserDao userDao2 = sqlSession.getMapper(UserDao.class);
User user = userDao.findById(1);
System.out.println(user2);
//4.测试两次结果是否一样
System.out.println(user1 == user2);//true
//5. 提交事务关闭资源
SqlSessionFactoryUtils.commitAndClose(sqlSession);
}
二级缓存
基本介绍
二级缓存是 mapper 的缓存,只要是同一个命名空间(namespace)的 SqlSession 就共享二级缓存的内容,并且可以操作二级缓存
作用:作用范围是整个应用,可以跨线程使用,适合缓存一些修改较少的数据
工作流程:一个会话查询数据,这个数据就会被放在当前会话的一级缓存中,如果会话关闭或提交一级缓存中的数据会保存到二级缓存
二级缓存的基本使用:
-
在 MyBatisConfig.xml 文件开启二级缓存,cacheEnabled 默认值为 true,所以这一步可以省略不配置
<!--配置开启二级缓存-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings> -
配置 Mapper 映射文件
<cache>
标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值<mapper namespace="dao.UserDao">
<!--开启user支持二级缓存-->
<cache eviction="FIFO" flushInterval="6000" readOnly="" size="1024"/>
<cache></cache> <!--则表示所有属性使用默认值-->
</mapper>eviction(清除策略):
LRU
– 最近最少使用:移除最长时间不被使用的对象,默认FIFO
– 先进先出:按对象进入缓存的顺序来移除它们SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象
flushInterval(刷新间隔):可以设置为任意的正整数, 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新
size(引用数目):缓存存放多少元素,默认值是 1024
readOnly(只读):可以被设置为 true 或 false
- 只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,促进了性能提升
- 可读写的缓存会(通过序列化)返回缓存对象的拷贝, 速度上会慢一些,但是更安全,因此默认值是 false
type:指定自定义缓存的全类名,实现 Cache 接口即可
-
要进行二级缓存的类必须实现 java.io.Serializable 接口,可以使用序列化方式来保存对象。
public class User implements Serializable{}
相关属性
-
select 标签的 useCache 属性
映射文件中的
<select>
标签中设置useCache="true"
代表当前 statement 要使用二级缓存(默认)注意:如果每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存
<select id="findAll" resultType="user" useCache="true">
select * from user
</select> -
每个增删改标签都有 flushCache 属性,默认为 true,代表在执行增删改之后就会清除一、二级缓存,保证缓存的一致性;而查询标签默认值为 false,所以查询不会清空缓存
-
localCacheScope:本地缓存作用域,
中的配置项,默认值为 SESSION,当前会话的所有数据保存在会话缓存中,设置为 STATEMENT 禁用一级缓存
源码解析
事务提交二级缓存才生效:DefaultSqlSession 调用 commit() 时会回调 executor.commit()
-
CachingExecutor#query():执行查询方法,查询出的数据会先放入 entriesToAddOnCommit 集合暂存
// 从二缓存中获取数据,获取不到去一级缓存获取
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 回调 BaseExecutor#query
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 将数据放入 entriesToAddOnCommit 集合暂存,此时还没放入二级缓存
tcm.putObject(cache, key, list);
} -
commit():事务提交,清空一级缓存,放入二级缓存,二级缓存使用 TransactionalCacheManager(tcm)管理
public void commit(boolean required) throws SQLException {
// 首先调用 BaseExecutor#commit 方法,【清空一级缓存】
delegate.commit(required);
tcm.commit();
} -
TransactionalCacheManager#commit:查询出的数据放入二级缓存
public void commit() {
// 获取所有的缓存事务,挨着进行提交
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}public void commit() {
if (clearOnCommit) {
delegate.clear();
}
// 将 entriesToAddOnCommit 中的数据放入二级缓存
flushPendingEntries();
// 清空相关集合
reset();
}private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
// 将数据放入二级缓存
delegate.putObject(entry.getKey(), entry.getValue());
}
}
增删改操作会清空缓存:
-
update():CachingExecutor 的更新操作
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
// 回调 BaseExecutor#update 方法,也会清空一级缓存
return delegate.update(ms, parameterObject);
} -
flushCacheIfRequired():判断是否需要清空二级缓存
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
// 判断二级缓存是否存在,然后判断标签的 flushCache 的值,增删改操作的 flushCache 属性默认为 true
if (cache != null && ms.isFlushCacheRequired()) {
// 清空二级缓存
tcm.clear(cache);
}
}
自定义
自定义缓存
<cache type="com.domain.something.MyCustomCache"/>
type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器
public interface Cache {
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
boolean hasKey(Object key);
Object removeObject(Object key);
void clear();
}
缓存的配置,只需要在缓存实现中添加公有的 JavaBean 属性,然后通过 cache 元素传递属性值,例如在缓存实现上调用一个名为 setCacheFile(String file)
的方法:
<cache type="com.domain.something.MyCustomCache">
<property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>
- 可以使用所有简单类型作为 JavaBean 属性的类型,MyBatis 会进行转换。
- 可以使用占位符(如
${cache.file}
),以便替换成在配置文件属性中定义的值
MyBatis 支持在所有属性设置完毕之后,调用一个初始化方法, 如果想要使用这个特性,可以在自定义缓存类里实现 org.apache.ibatis.builder.InitializingObject
接口
public interface InitializingObject {
void initialize() throws Exception;
}
注意:对缓存的配置(如清除策略、可读或可读写等),不能应用于自定义缓存
对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新,在多个命名空间中共享相同的缓存配置和实例,可以使用 cache-ref 元素来引用另一个缓存
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
构造语句
动态 SQL
基本介绍
动态 SQL 是 MyBatis 强大特性之一,逻辑复杂时,MyBatis 映射配置文件中,SQL 是动态变化的,所以引入动态 SQL 简化拼装 SQL 的操作
DynamicSQL 包含的标签:
- if
- where
- set
- choose (when、otherwise)
- trim
- foreach
各个标签都可以进行灵活嵌套和组合
OGNL:Object Graphic Navigation Language(对象图导航语言),用于对数据进行访问
参考文章:https://www.cnblogs.com/ysocean/p/7289529.html
where
作用:如果标签返回的内容是以 AND 或 OR 开头的,标签内会剔除掉
表结构:
if
基本格式:
<if test=“条件判断”>
查询条件拼接
</if>
我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。比如在 id 如果不为空时可以根据 id 查询,如果username 不同空时还要加入用户名作为条件,这种情况在我们的多条件组合查询中经常会碰到。
-
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="mapper.UserMapper">
<select id="selectCondition" resultType="user" parameterType="user">
SELECT * FROM user
<where>
<if test="id != null ">
id = #{id}
</if>
<if test="username != null ">
AND username = #{username}
</if>
<if test="sex != null ">
AND sex = #{sex}
</if>
</where>
</select>
</mapper> -
MyBatisConfig.xml,引入映射配置文件
<mappers>
<!--mapper引入指定的映射配置 resource属性执行的映射配置文件的名称-->
<mapper resource="UserMapper.xml"/>
</mappers> -
DAO 层 Mapper 接口
public interface UserMapper {
//多条件查询
public abstract List<User> selectCondition(Student stu);
} -
实现类
public class DynamicTest {
@Test
public void selectCondition() throws Exception{
//1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = ssf.openSession(true);
//4.获取StudentMapper接口的实现类对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setId(2);
user.setUsername("李四");
//user.setSex(男); AND 后会自动剔除
//5.调用实现类的方法,接收结果
List<Student> list = mapper.selectCondition(user);
//6.处理结果
for (User user : list) {
System.out.println(user);
}
//7.释放资源
sqlSession.close();
is.close();
}
}
set
<!-- 根据 id 更新 user 表的数据 -->
<update id="updateUserById" parameterType="com.ys.po.User">
UPDATE user u
<set>
<if test="username != null and username != ''">
u.username = #{username},
</if>
<if test="sex != null and sex != ''">
u.sex = #{sex}
</if>
</set>
WHERE id=#{id}
</update>
- 如果第一个条件 username 为空,那么 sql 语句为:update user u set u.sex=? where id=?
- 如果第一个条件不为空,那么 sql 语句为:update user u set u.username = ? ,u.sex = ? where id=?
choose
假如不想用到所有的查询条件,只要查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句
标签:
<select id="selectUserByChoose" resultType="user" parameterType="user">
SELECT * FROM user
<where>
<choose>
<when test="id !='' and id != null">
id=#{id}
</when>
<when test="username !='' and username != null">
AND username=#{username}
</when>
<otherwise>
AND sex=#{sex}
</otherwise>
</choose>
</where>
</select>
有三个条件,id、username、sex,只能选择一个作为查询条件
-
如果 id 不为空,那么查询语句为:select * from user where id=?
-
如果 id 为空,那么看 username 是否为空
- 如果不为空,那么语句为:select * from user where username=?
- 如果 username 为空,那么查询语句为 select * from user where sex=?
trim
trim 标记是一个格式化的标记,可以完成 set 或者是 where 标记的功能,自定义字符串截取
- prefix:给拼串后的整个字符串加一个前缀,trim 标签体中是整个字符串拼串后的结果
- prefixOverrides:去掉整个字符串前面多余的字符
- suffix:给拼串后的整个字符串加一个后缀
- suffixOverrides:去掉整个字符串后面多余的字符
改写 if + where 语句:
<select id="selectUserByUsernameAndSex" resultType="user" parameterType="com.ys.po.User">
SELECT * FROM user
<trim prefix="where" prefixOverrides="and | or">
<if test="username != null">
AND username=#{username}
</if>
<if test="sex != null">
AND sex=#{sex}
</if>
</trim>
</select>
改写 if + set 语句:
<!-- 根据 id 更新 user 表的数据 -->
<update id="updateUserById" parameterType="com.ys.po.User">
UPDATE user u
<trim prefix="set" suffixOverrides=",">
<if test="username != null and username != ''">
u.username = #{username},
</if>
<if test="sex != null and sex != ''">
u.sex = #{sex},
</if>
</trim>
WHERE id=#{id}
</update>
foreach
基本格式:
<foreach>:循环遍历标签。适用于多个参数或者的关系。
<foreach collection=“”open=“”close=“”item=“”separator=“”>
获取参数
</foreach>
属性:
- collection:参数容器类型, (list-集合, array-数组)
- open:开始的 SQL 语句
- close:结束的 SQL 语句
- item:参数变量名
- separator:分隔符
需求:循环执行 sql 的拼接操作,SELECT * FROM user WHERE id IN (1,2,5)
-
UserMapper.xml片段
<select id="selectByIds" resultType="user" parameterType="list">
SELECT * FROM student
<where>
<foreach collection="list" open="id IN(" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select> -
测试代码片段
//4.获取StudentMapper接口的实现类 对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<Integer> ids = new ArrayList<>();
Collections.addAll(list, 1, 2);
//5.调用实现类的方法,接收结果
List<User> list = mapper.selectByIds(ids);
for (User user : list) {
System.out.println(user);
}
SQL片段
将一些重复性的 SQL 语句进行抽取,以达到复用的效果
格式:
<sql id=“片段唯一标识”>抽取的SQL语句</sql> <!--抽取标签-->
<include refid=“片段唯一标识”/> <!--引入标签-->
使用:
<sql id="select">SELECT * FROM user</sql>
<select id="selectByIds" resultType="user" parameterType="list">
<include refid="select"/>
<where>
<foreach collection="list" open="id IN(" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
逆向工程
MyBatis 逆向工程,可以针对单表自动生成 MyBatis 执行所需要的代码(mapper.java、mapper.xml、pojo…)
generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatisrelation" userId="root"
password="root">
</jdbcConnection>
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL和NUMERIC类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置!! -->
<javaModelGenerator targetPackage="com.ys.po"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置!! -->
<sqlMapGenerator targetPackage="com.ys.mapper"
targetProject=".\src">
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置,重要!! -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.ys.mapper"
targetProject=".\src">
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表,要生成哪些表,就写哪些表,要和数据库中对应,不能写错! -->
<table tableName="items"></table>
<table tableName="orders"></table>
<table tableName="orderdetail"></table>
<table tableName="user"></table>
</context>
</generatorConfiguration>
生成代码:
public void testGenerator() throws Exception{
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
//指向逆向工程配置文件
File configFile = new File(GeneratorTest.class.
getResource("/generatorConfig.xml").getFile());
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
参考文章:https://www.cnblogs.com/ysocean/p/7360409.html
构建 SQL
基础语法
MyBatis 提供了 org.apache.ibatis.jdbc.SQL 功能类,专门用于构建 SQL 语句
方法 | 说明 |
---|---|
SELECT(String... columns) | 根据字段拼接查询语句 |
FROM(String... tables) | 根据表名拼接语句 |
WHERE(String... conditions) | 根据条件拼接语句 |
INSERT_INTO(String tableName) | 根据表名拼接新增语句 |
INTO_VALUES(String... values) | 根据值拼接新增语句 |
UPDATE(String table) | 根据表名拼接修改语句 |
DELETE_FROM(String table) | 根据表名拼接删除语句 |
增删改查注解:
- @SelectProvider:生成查询用的 SQL 语句
- @InsertProvider:生成新增用的 SQL 语句
- @UpdateProvider:生成修改用的 SQL 语句注解
- @DeleteProvider:生成删除用的 SQL 语句注解。
- type 属性:生成 SQL 语句功能类对象
- method 属性:指定调用方法
基本操作
-
MyBatisConfig.xml 配置
<!-- mappers引入映射配置文件 -->
<mappers>
<package name="mapper"/>
</mappers> -
Mapper 类
public interface StudentMapper {
//查询全部
@SelectProvider(type = ReturnSql.class, method = "getSelectAll")
public abstract List<Student> selectAll();
//新增数据
@InsertProvider(type = ReturnSql.class, method = "getInsert")
public abstract Integer insert(Student student);
//修改操作
@UpdateProvider(type = ReturnSql.class, method = "getUpdate")
public abstract Integer update(Student student);
//删除操作
@DeleteProvider(type = ReturnSql.class, method = "getDelete")
public abstract Integer delete(Integer id);
} -
ReturnSQL 类
public class ReturnSql {
//定义方法,返回查询的sql语句
public String getSelectAll() {
return new SQL() {
{
SELECT("*");
FROM("student");
}
}.toString();
}
//定义方法,返回新增的sql语句
public String getInsert(Student stu) {
return new SQL() {
{
INSERT_INTO("student");
INTO_VALUES("#{id},#{name},#{age}");
}
}.toString();
}
//定义方法,返回修改的sql语句
public String getUpdate(Student stu) {
return new SQL() {
{
UPDATE("student");
SET("name=#{name}","age=#{age}");
WHERE("id=#{id}");
}
}.toString();
}
//定义方法,返回删除的sql语句
public String getDelete(Integer id) {
return new SQL() {
{
DELETE_FROM("student");
WHERE("id=#{id}");
}
}.toString();
}
} -
功能实现类
public class SqlTest {
@Test //查询全部
public void selectAll() throws Exception{
//1.加载核心配置文件
InputStream is = Resources.getResourceAsStream("MyBatisConfig.xml");
//2.获取SqlSession工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3.通过工厂对象获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//4.获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//5.调用实现类对象中的方法,接收结果
List<Student> list = mapper.selectAll();
//6.处理结果
for (Student student : list) {
System.out.println(student);
}
//7.释放资源
sqlSession.close();
is.close();
}
@Test //新增
public void insert() throws Exception{
//1 2 3 4获取StudentMapper接口的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//5.调用实现类对象中的方法,接收结果 ->6 7
Student stu = new Student(4,"赵六",26);
Integer result = mapper.insert(stu);
}
@Test //修改
public void update() throws Exception{
//1 2 3 4 5调用实现类对象中的方法,接收结果 ->6 7
Student stu = new Student(4,"赵六wq",36);
Integer result = mapper.update(stu);
}
@Test //删除
public void delete() throws Exception{
//1 2 3 4 5 6 7
Integer result = mapper.delete(4);
}
}