Mybatis
MyBatis ,是国内最火的持久层框架
采用了ORM思想解决了实体类和数据库表映射的问题。对JDBC进行了封装,屏蔽了JDBCAPI底层的访问细节,避免我们与jdbc的api打交 道,就能完成对数据的持久化操作。
O--Object java对象
R- Relation 关系,就是数据库中的一张表
M-mapping 映射
一、快速开始
Mybatis 官方帮助文档: https://mybatis.org/mybatis-3/zh/index.html
1、创建 Maven 项目
2、导入 Maven 依赖
这里,我们要导入 mybatis的依赖、mysql 的一类、单元测试的依赖
<dependencies>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
3、配置 Maven 插件
这里,要配置的,是 Maven 的编译插件,我们指定源文件和编译后的文件都是 java 1.8 版本的
<build>
<plugins>
<!--编译插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
4、新建数据库,导入表格
CREATE TABLE `team` (
`teamId` int NOT NULL AUTO_INCREMENT COMMENT '球队ID',
`teamName` varchar(50) DEFAULT NULL COMMENT '球队名称',
`location` varchar(50) DEFAULT NULL COMMENT '球队位置',
`createTime` date DEFAULT NULL COMMENT '球队建立时间',
PRIMARY KEY (`teamId`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
5、编写 Mybatis 配置文件
我们可以直接在官网,获取配置文件的示例
日后在开发时,建议读者创建一个文档,存放配置文件的编写规则,方便开发
<property>中的内容,可以通过配置文件的方式获取,我们后面再介绍
<?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>
<!--mybatis 环境,可以为 mybatis 配置多套环境,然后根据 id 切换-->
<environments default="development">
<environment id="development">
<!--事务类型:使用 JDBC 事务,使用 Connection 的提交和回滚-->
<transactionManager type="JDBC"/>
<!--数据源 dataSource:创建数据库 Connection 对象 type: POOLED 使用数据库的连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis_study?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT"/>
<property name="username" value="admin"/>
<property name="password" value="123"/>
</dataSource>
</environment>
</environments>
<!--mapper映射,这里我们先不写,后面再讲-->
<!--<mappers>-->
<!-- <mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
<!--</mappers>-->
</configuration>
6、编写实体类
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Team {
private Integer teamId;
private String teamName;
private String location;
private Date createTime;
}
7、编写 mapper 接口
public interface TeamMapper {
List<Team> getAll();
}
8、编写 mapper 实现
实现,是用 .xml 文件编写的
id:接口中非方法
resultTyoe:接口方法的返回值(在配置前,必须写全类名)
注意: XxxMapper.xml,必须要和对应的 mapper 接口,同包同名
<?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绑定一个指定的Dao/Mapper接口-->
<mapper namespace="top.faroz.dao.TeamMapper">
<select id="getAll" resultType="top.faroz.pojo.Team">
select * from team
</select>
</mapper>
也可以将 .xml 实现,放在 resources 下,但是一定要注意,同包同名:
9、Mybatis 配置文件中,添加 mapper 映射
<!--mapper映射-->
<mappers>
<package name="top.faroz.dao"/>
</mappers>
编写完 .xml 实现后,一定要在配置文件中,添加 mapper 映射
<!--mapper映射--> <mappers> <package name="top.faroz.dao"/> </mappers>
10、编写 Mybatis 工具类
工具类用来创建 sqlSession的单例工厂
并添加一个方法,用来获取 sqlSession 连接
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
//使用MyBatis第一步:获取SqlSessionFactory对象
try {
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
//设置为true,自动提交事务
return sqlSessionFactory.openSession(true);
}
}
11、测试
@Test
public void getAllTest() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
List<Team> all = mapper.getAll();
for (Team team : all) {
System.out.println(team);
}
sqlSession.close();
}
测试结果如下:
二、日志添加
1、添加 Maven 依赖
<!--log4j日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、添加 log4j 配置
log4j.properties
我们可以调整日志输出的级别,除了 DEBUG外,还可以有 INFO,WARNING , ERROR
# Global logging configuration info warning error
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
现在是在控制台输出,未来,可能我们的项目,是要部署在客户的服务器上的,我们可以将日志信息固定输出到某个外部文件当中
3、Mybatis 中配置 LOG
<!--配置日志-->
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
4、执行测试
控制台会显示详细的日志信息
三、Mybatis 对象分析
1、Resources
Resources 类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析资源文件,返回不同类型的 IO 流对象。
2、SqlSessionFactoryBuilder
用来创建 SqlSessionFactory 的创造者,在整个项目中,只会使用一次,就是用来创建 SqlSessionFactory 的。用后即丢
3、SqlSessionFactory
创建 sqlSession 的单例工厂。在整个项目中,应该只有一个 SqlSessionFactory 的单例
4、SqlSession
一个 SqlSession 对应着一次数据库会话,一次会话以SqlSession 对象的创建开始,以 SqlSession 对象的关闭结束。
SqlSession 接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其 close()方法,将其关闭。再次需要会话,再次创建。
四、改进 MybatisUtil
在 快速开始 栏目中,我们将获取 Mybatis 的 sqlSession 对象的过程,封装成了一个工具类
但是,sqlSession 并没有作为一个成员变量,存储在 MybatisUtil 中,这样,我们对 sqlSession 执行 close ,必须要手动去调用我们外部获得的 sqlSession ,其实,我们可以将 sqlSession 变成一个成员变量,然后,在 MybatisUtil 中,写一个close 方法。
但是,我们要注意,sqlSession 是线程不安全的,每次使用完后,我们都需要进行 close,然后,在下次使用的时候,再次连接 sqlSession ,如果把 sqlSession ,作为一个静态成员变量,放在 MybatisUtil 中,势必会引发线程相关的问题。
为了解决这个问题,我们需要来介绍一下 ThreadLocal
1、ThreadLocal
2、使用 ThreadLocal 来改写
3、小结
我个人其实是不建议将 sqlSession 作为一个静态成员变量的,并且官方文档也不建议我们这么做。我写这一小节的主要目的,还是为了介绍一下 ThreadLocal
五、输入映射
之前,我们的测试中,输入都是单个值,如果我们的输入中,有多个值,该怎么办呢?
1、使用下标(不推荐)
如果有多个参数,可以使用:
#{arg0},#{arg1},#{arg2}…,或者#{param0},#{param2},#{param3}…的方式,获取不同参数
但是这种方式,不推荐使用
2、使用注解
我们可以在对应的 mapper 接口的参数前,加上 @Param 注解,并且在 .xml 实现类中,直接使用注解中定义的参数名
接口:
Team selectByNameAndLocation(@Param("name") String name,@Param("location") String location);
实现:
<select id="selectByNameAndLocation" resultType="top.faroz.pojo.Team">
select * from team where teamName=#{name} and location=#{location}
</select>
测试:
@Test
public void selectByNameAndLocationTest() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
Team team = mapper.selectByNameAndLocation("雄鹿", "威斯康星州密尔沃基");
System.out.println(team);
sqlSession.close();
}
3、使用 map
传入的参数,可以为 map ,.xml文件中,获取的参数,要和 map 中的 key 值保持一致
接口:
Team selectByName(Map map);
实现:
<select id="selectByName" resultType="top.faroz.pojo.Team" parameterType="map">
select * from team where teamName=#{name}
</select>
测试:
@Test
public void selectByNameTest() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("name","雄鹿");
Team team = mapper.selectByName(map);
System.out.println(team);
sqlSession.close();
}
六、输出映射
1、resultType
1)、输出简单类型
就是一般的,输出单个 String 或者 Integer 类型,前面的示例有很多,这里不再演示了
2)、输出 pojo 类型
List<Team> queryAll();
<!--接口方法返回是集合类型,但是映射文件中的resultType需要指定集合中的类型,不是集合本身。--> <select id="queryAll" resultType="com.kkb.pojo.Team">
select * from team;
</select>
3)、输出 map 类型
当我们只需要查询表中几列数据的时候可以将sql的查询结果作为Map的key和value。一般使用的是Map<Object,Object>。
Map 作为接口返回值,sql 语句的查询结果最多只能有一条记录。大于一条记录会抛出TooManyResultsException异常。 如果有多行,使用List<Map<Object,Object>>。
Map<Object,Object> queryTwoColumn(int teamId);
List<Map<Object,Object>> queryTwoColumnList();
<select id="queryTwoColumn" resultType="java.util.HashMap" paramType="int">
select teamName,location from team where teamId = #{id}
</select>
<select id="queryTwoColumnList" resultType="java.util.HashMap">
select teamName,location from team
</select>
@Test
public void test08(){
Map<String, Object> map = teamMapper.queryTwoColumn(); System.out.println(map);
}
@Test
public void test09(){
List<Map<String, Object>> list = teamMapper.queryTwoColumnList();
for (Map<String, Object> map : list) {
System.out.println(map);
}
}
2、resultMap
resultMap 可以自定义 sql 的结果和 java 对象属性的映射关系。更灵活的把列值赋值给指定属性。
一般主键列用id , 其余列用result
column:表示数据库表中的列名,不区分大小写 property:表示实体类中的对应的属性名,区分大小写 javaType:
实体类中的对应的属性的类型,可以省略,mybatis会自己推断 jdbcType="数据库中的类型column的类型" 一般省略
<resultMap id="baseResultMap" type="com.kkb.pojo.Team">
<!--主键列,使用 id-->
<id column="teamId" property="teamId" javaType="java.lang.Integer"></id>
<!--其余列,使用 result-->
<result column="teamName" property="teamName" javaType="java.lang.String"></result>
<result column="location" property="location" javaType="java.lang.String"></result>
<result column="createTime" property="createTime" javaType="java.util.Date"></result>
</resultMap>
实现语句,使用的时候,返回值就由原来的 resultType,改为我们写的 resultMap:
<select id="queryAll2" resultMap="baseResultMap">
select * from team;
</select>
使用 resultMap 进行属性映射,还可以解决属性名与数据库表列名不一致的问题
3、数据库表中列与实体类属性不一致的处理方式
1)、使用 resultMap 去解决
上面讲过,这里就不再演示了
2)、sql 中起别名
假如 sql 中的命名方式,是使用下划线方式的,而 pojo 中,使用的是驼峰命名方式,那我们可以用如下起别名的方式,将查询出的结果,换成驼峰命名方式:
select user_id as userId,user_name as userName,user_age as userAge from users where user_id=#{id};
七、#{} 和 ${} 的区别
这个问题,也是面试题常考的
#{}:表示一个占位符,通知Mybatis 使用实际的参数值代替。并使用 PrepareStatement 对象执行 sql 语句, #{…}代替sql 语句的“?”。这个是Mybatis 中的首选做法,安全迅速。
通过这种方式,可以防止 sql 注入。
${}:表示字符串原样替换,通知Mybatis 使用$包含的“字符串”替换所在位置。使用 Statement或者PreparedStatement 把 sql 语句和${} 的内容连接起来。一般用在替换表名, 列名,不同列排序等操作。
这种方式,可以修改 sql 语句的列,比如说,我们有这么个需求:
需要分别根据 name,age,address 等查询用户信息,按照一般的写法,就要写 3 个 sql
select * from xxx where name = #{name}
select * from xxx where age = #{age}
select * from xxx where address = #{address}
我们可以看到,不同的,只是 where 后面的部分,这里,我们就可以用 ${}去替换 where 后面的部分,从而不用写那么多 sql
select * from xxx where ${column} = #{columnValue}
这里要注意,因为是字符串拼接非方式,所以,${}千万不能用在参数上面,不然可能会产生 sql 注入的问题。但为什么可以用在 sql 的其他位置?这是因为,如果在非参数的地方,写上 sql 注入的语句,就会造成 sql 语句错误,从而报错。
八、Mybatis 全局配置文件
1、配置内容总览
配置的时候,少几个配置没有关系,但是一定要按照下面的顺去配置
configuration(配置)
properties--属性:加载外部的配置文件,例如加载数据库的连接信息
Settings--全局配置参数:例如日志配置
typeAliases--类型别名
typeHandlers----类型处理器
objectFactory-----对象工厂
Plugins------插件:例如分页插件
Environments----环境集合属性对象
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
Mappers---映射器:注册映射文件用
2、属性(properties)
可以通过属性配置,让 Mybatis 去读取外部的配置文件,比如加载数据库的连接信息
1)、新建配置文件
在 resources 文件夹下,建立 jdbc.properties 配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis_study?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
jdbc.username=admin
jdbc.password=123
2)、mybatis 配置中,引入配置文件信息
<!--加载配置文件-->
<properties resource="jdbc.properties"/>
3)、读取配置文件
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
3、设置(settings)
MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为.例如我们配置的日志就是应用之一。其余内容参考设置文档
https://mybatis.org/mybatis-3/zh/configuration.html#settings
4、类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
1)已支持的别名
2)自定义别名
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。
(下面👇这个方法应该更便捷)
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<package name="top.faroz.pojo"/>
</typeAliases>
<select id="getUserList" resultType="user">
select * from user
</select>
5、 映射器(Mappers)
方式一:【推荐使用】
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
方式二:使用class文件绑定
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
注意点:
接口和配置文件同名接口和它的Mapper配置文件必须在同一个包下
方式三:使用扫描包进行注入绑定
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
6、数据源(dataSource)
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 (数据库连接池)JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
默认使用POOLED
7、事务(transactionManager
)
1)、默认是需要手动提交事务的
Mybatis 框架是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC 的 Connection对象的 commit(), rollback().Connection 对象的 setAutoCommit()方法来 设置事务提交方式的。
自动提交和手工提交、<transactionManager type="JDBC"/>该标签用于指定 MyBatis所使用的事务管理器。MyBatis 支持两种事务管理器类型:JDBC 与 MANAGED。
JDBC:使用JDBC的事务管理机制,通过Connection对象的 commit()方法提交,通过rollback()方法 回滚。默认情况下,mybatis将自动提交功能关闭了,改为了手动提交,观察日志可以看出,所以我们在程序中都需要自己提交事务或者回滚事务。
MANAGED:由容器来管理事务的整个生命周期(如Spring容器)。
2)、自动提交事务
九、关系映射
1、对一关系
有这么一个需求,有许多学生,很多学生对应某个老师,现在要将学生和对应老师的属性查询出来
SQL语句:
# 学生表
drop table if exists `student`;
CREATE TABLE `student` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '学生 id',
`name` varchar(50) DEFAULT NULL COMMENT '学生姓名',
`tid` int DEFAULT NULL COMMENT '学生所属老师 id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
insert into student value (1,'jojo',1);
insert into student value (2,'dio',1);
insert into student value (3,'faro',2);
insert into student value (4,'kkk',2);
insert into student value (5,'ttt',3);
# 老师表
drop table if exists `teacher`;
CREATE TABLE `teacher` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '老师 id',
`name` varchar(50) DEFAULT NULL COMMENT '老师姓名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1003 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
insert into teacher value (1,'老师1');
insert into teacher value (2,'老师2');
insert into teacher value (3,'老师3');
学生:
public class Student {
private int id;
private String name;
private Teacher teacher;
}
老师:
public class Teacher {
private int id;
private String name;
}
1)、按照查询嵌套处理
接口:
List<Student> getAll();
.xml
实现
相当于在 resultMap 中,再执行一次查询
<select id="getAll" resultMap="stusta">
select * from student
</select>
<resultMap id="stusta" type="Student" >
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="tid" column="tid"/>
<!--这里的tid的值,会传递到 getTeacher 中,从而查询出对应教师-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{id}
</select>
测试:
@Test
public void getAllTest() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> all = mapper.getAll();
for (Student student : all) {
System.out.println(student);
}
sqlSession.close();
}
2)、按照结果嵌套处理
这种方法类似于 联表查询
<!--方法2:按照结果嵌套处理(这种方式更好懂)-->
<select id="getStudentList" resultMap="StudentTeacher">
select s.id sid,s.name sname,t.name tname,t.id tid
from student s,teacher t
where s.tid=t.id
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
按照方便程度来说,明显是第一种方式比较方便(sql写的少)
但是按照性能来说,是第二种性能更好(毕竟只要查询一次数据库)
2、对多关系
一对多的方式和多对一差不多
比如:一个老师拥有多个学生
环境搭建,和刚才一样编写实体类
public class Student {
private int id;
private String name;
private int tid;
}
public class Teacher {
private int id;
private String name;
//一个老师多个学生
private List<Student> students;
}
1)、按照查询嵌套查询
接口:
List<Teacher> getAll();
.xml
实现
这里的 ofType,指明的是集合中元素的泛型
<select id="getAll" resultMap="getStaStu">
select * from teacher
</select>
<resultMap id="getStaStu" type="Teacher">
<id column="id" property="id"/>
<result column="name" property="name"/>
<!--这里的id ,是老师的id ,会自动映射到子查询的中的 tid 中-->
<collection property="students" column="id" javaType="ArrayList" ofType="Student" select="selectStudentByTid"/>
</resultMap>
<select id="selectStudentByTid" resultType="Student">
select * from student where tid =#{tid}
</select>
测试:
@Test
public void getAllTest() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
List<Teacher> all = mapper.getAll();
for (Teacher teacher : all) {
System.out.println(teacher);
}
sqlSession.close();
}
2)、按照结果嵌套查询
接口:
List<Teacher> getAll();
.xml
实现:
<select id="getAll" resultMap="getStaStu">
select teacher.id tid,teacher.name tname,student.id sid,student.name sname
from teacher,student
where teacher.id=student.tid;
</select>
<resultMap id="getStaStu" type="Teacher">
<id column="tid" property="id"/>
<result column="tname" property="name"/>
<collection property="students" javaType="ArrayList" ofType="Student">
<result column="sid" property="id"/>
<result column="sname" property="name"/>
</collection>
</resultMap>
测试:
@Test
public void getAllTest() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
List<Teacher> all = mapper.getAll();
for (Teacher teacher : all) {
System.out.println(teacher);
}
sqlSession.close();
}
十、动态 SQL
使用动态 SQL ,避免了 SQL 拼接的烦恼
where标签在select中的使用
注意: 在 mybatis 的 xml 实现中 >可以直接使用,但是 <不可以直接使用,必须使用转移符号
<select id="queryByVO" parameterType="QueryVO" resultMap="baseResultMap">
select * from team
<where>
<!-- 如果用户输入了名称,就模糊查询 and teamName like '%?%'-->
<if test="name!=null ">
and teamName like concat(concat('%',#{name}),'%')
</if>
<if test="beginTime!=null ">
and createTime>=#{beginTime}
</if>
<if test="endTime!=null ">
and createTime<=#{endTime}
</if>
<if test="location!=null ">
and location=#{location}
</if>
</where>
</select>
模糊查询:
使用模糊查询的时候,一定要使用 concat 函数,将字符串进行拼接,不能使用 + 号
<select id="getByName" resultType="top.faroz.pojo.Teacher" parameterType="string">
select * from teacher
<where>
<if test="name!=null">
-- 使用 concat 函数,将多个字符串进行拼接
and name like concat(concat('%',#{name}),'%')
</if>
</where>
</select>
set标签在update中的使用
<update id="update1" parameterType="com.kkb.pojo.Team">
update team
<set>
<if test="teamName!=null">
teamName=#{teamName},
</if>
<if test="location!=null">
location=#{location},
</if>
<if test="createTime!=null">
createTime=#{createTime},
</if>
</set>
where teamId=#{teamId}
</update>
forEach标签
批量添加
<insert id="addList" parameterType="arraylist">
INSERT INTO team (teamName,location) VALUES
<!--collection:要遍历的集合;参数是集合类型,直接写list item:遍历的集合中的每一个数据 separator:将遍历的结果用,分割-->
<foreach collection="list" item="t" separator=",">
(#{t.teamName},#{t.location})
</foreach>
</insert>
批量删除
<delete id="delList" >
delete from team where teamId in
<!--collection:要遍历的集合;参数是集合类型,直接写list item:遍历的集合中的每一个数据separator:将遍历的结果用,分割
open="(" close=")":表示将遍历结果用open close包裹起来-->
<foreach collection="list" item="teamId" separator="," open="(" close=")">
#{teamId}
</foreach>
</delete>
SQL
片段
提取SQL片段:
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
引用SQL片段:
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
<include refid="if-title-author"></include>
<!-- 在这里还可以引用其他的 sql 片段 -->
</where>
</select>
注意:
①、最好基于 单表来定义 sql 片段,提高片段的可重用性
②、在 sql 片段中不要包括 where
十一、分页插件
分页插件,我们使用 pageHelper
1、快速开始
1)、Maven 依赖
<!--pagehelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
2)、Mybatis全局配置文件中添加插件配置
<plugins>
<!-- 引入 pageHelper插件 -->
<!--注意这里要写成PageInterceptor, 5.0之前的版本都是写PageHelper, 5.0之后要换成PageInterceptor-->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--reasonable:分页合理化参数,默认值为false,直接根据参数进行查询。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。 方言可以省略,会根据连接数据的参数url自动推断-->
<!--<property name="reasonable" value="true"/>-->
</plugin>
</plugins>
3)、使用插件
2、PageHelper 介绍
PageHelper 会拦截查询语句,然后添加分页语句
@Test
public void getAllTest() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
PageHelper.startPage(2,5);
List<Team> all = mapper.getAll();
for (Team team : all) {
System.out.println(team);
}
sqlSession.close();
}
3、PageInfo 介绍
可以用来打印分页的相关信息,如总条数,当前页数,总页数等
@Test
public void getAllTest() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
PageHelper.startPage(2,5);
List<Team> all = mapper.getAll();
for (Team team : all) {
System.out.println(team);
}
PageInfo<Team> pageInfo = new PageInfo<>(all);
System.out.println("分页信息如下:");
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页数:"+pageInfo.getPageNum());
System.out.println("每页条数:"+pageInfo.getPageSize());
sqlSession.close();
}
十二、缓存
通过使用缓存,可以在第一次查询的时候,将查询到的信息,放入缓存,这样,在第二次查询的时候,就会走缓存,从而,减轻数据库压力、提高查询效率,解决高并发问题。
MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。
1、一级缓存
自动开启,SqlSession级别的缓存
在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数 据将不再从数据库查询,从而提高查询效率。
当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。 Mybatis默认开启一级缓存,存在内存中(本地缓存)不能被关闭,可以调用clearCache()来清空本地缓存,或者改变缓存的作用域。
1)、一级缓存介绍
当用户发起第一次查询team=1001的时候,先去缓存中查找是否有team=1001的对象;如果没有,继续向数据中发送查询语句,查询成功之后会将teamId=1001的结果存入缓存 中;
当用户发起第2次查询team=1001的时候,先去缓存中查找是否有team=1001的对象,因为第一次查询成功之后已经存储到缓存中,此时可以直接从缓存中获取到该数据,意味 着不需要再去向数据库发送查询语句。
如果SqlSession执行了commit(有增删改的操作),此时该SqlSession对应的缓存区域被整个清空,目的避免脏读。
**前提:**SqlSession未关闭。
2)、清空缓存的方式
1s、 session.clearCache( ) ;
2、 execute update(增删改) ;
3、 session.close( );
4、 xml配置 flushCache="true" ;
5、 rollback;
6、 commit。
2、二级缓存
Mapper 级别的缓存,可以跨 sqlSession
1)、二级缓存介绍
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace。
不同的sqlSession两次执行相同namespace下的sql语句参数相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获 取数据将不再从数据库查询,从而提高查询效率。
Mybatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存。 如果缓存中有数据就不用从数据库中获取,大大提高系统性能。
与一级缓存一样,一旦反生 增删改,并commit,就会清空缓存的数据,从而避免数据脏读
其原理图如下:
2)、使用二级缓存
3)、二级缓存的禁用
为什么需要禁用二级缓存?
在某些情况下,有一些数据被修改的频率是十分频繁的,一旦开启二级缓存,那么对其缓存清空的操作也会十分频繁,从而增大数据库的压力,我们可以但为这些 sql 查询,关闭二级缓存:
在开始了二级缓存的XML中对应的statement中设置useCache=false禁用当前Select语句的二级缓存,意味着该SQL语句每次只需都去查询数据库,不会查询缓存。 useCache默认值是true。对于一些很重要的数据尽不放在二级缓存中。
4)、缓存的属性配置
<cache>
<property name="eviction" value="LRU"/><!--回收策略为LRU-->
<property name="flushInterval" value="60000"/><!--自动刷新时间间隔为60S-->
<property name="size" value="1024"/><!--最多缓存1024个引用对象-->
<property name="readOnly" value="true"/><!--只读-->
</cache>
源码如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface CacheNamespace {
Class<? extends Cache> implementation() default PerpetualCache.class;
Class<? extends Cache> eviction() default LruCache.class;
long flushInterval() default 0L;
int size() default 1024;
boolean readWrite() default true;
boolean blocking() default false;
Property[] properties() default {};
}
如果想在命名空间中共享相同的缓存配置和实例,可以使用cache-ref 元素来引用另外一个缓存。
所谓命名空间,其实就是一个个 xml 实现
<cache-ref namespace="com.kkb.mapper.TeamMapper" />
//引用TeamMapper命名空间中的cache。
十三、反向生成器
使用反向生成器,就可以根据数据库表格,去自动生成持久层的代码
1、配置
略
2、使用
这里介绍其中的部分使用注意点
1)、动态更新/插入
带 selective 关键字的,表示动态改变
//动态插入
mapper.insertSelective(User user);
//动态更新
mapper.updateByPrimaryKeySelective(User user);
对于插入,是对插入对象中,属性为空的地方,不写上插入 sql
对于更新,是对属性为空的部分,不执行更新操作()即不会用新对象的空洞部分,去覆盖元数据区
2)、多条件查询
多条件查询,需要用到 XxxExample
使用步骤如下:
//1、创建被查询对象的 Example 对象
EbookExample ebookExample = new EbookExample();
//2、创建盛放查询条件的容器
EbookExample.Criteria criteria = ebookExample.createCriteria();
//3、在容器中,防止多查询条件
criteria.andNameLike("spring");//模糊查询
criteria.andNameEqualTo("spr");//等于
//...还有很多,对于每个属性,都有等量的多查询条件可供选择
//4、传入 Example 对象,进行多条件查询
mapper.selectByExample(ebookExample);
十四.总结
以上就是本文针对MyBatis入门学习教程-MyBatis快速入门的全部内容,希望大家多多关注编程网的其他内容!