文章目录
mybatis官网:https://mybatis.org/mybatis-3/zh/getting-started.html
搭建环境
导入依赖,以及修改过滤规则
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
创建mybatis的配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 设置实体类的别名 -->
<typeAliases>
</typeAliases>
<!-- 数据库连接 -->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/angenin?useSSL=false&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 注册mapper文件 -->
<mappers>
</mappers>
</configuration>
地址报红解决办法:
编写工具类
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
private static String resource = "mybatis-config.xml";
static {
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
// 创建SqlSessionFactory工厂对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
// 获取SqlSession对象
public static SqlSession getSqlSession() {
// 往openSession()中传入true后,生成的SqlSession会自动提交事务
return sqlSessionFactory.openSession();
}
}
创建数据表
create table t_users(
`id` int primary key auto_increment,
`name` varchar(15) not null,
`password` varchar(20) not null
);
insert into t_users(name, password)values("111", "111");
insert into t_users(name, password)values("222", "222");
创建user实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private int id;
private String name;
private String password;
}
创建UserMapper接口
public interface UserMapper {
List<User> getUserList();
}
创建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="com.angenin.dao.UserMapper">
<select id="getUserList" resultType="com.angenin.pojo.User">
select * from t_users;
</select>
</mapper>
在mybatis-config.xml里注册UserMapper
<!-- 注册mapper文件 -->
<mappers>
<!-- 目录按斜杠/分隔 -->
<mapper resource="com/angenin/dao/UserMapper.xml"/>
</mappers>
测试
@Test
public void test01() {
// 获取sqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
// 获取UserMapper对象
UserMapper userDAO = sqlSession.getMapper(UserMapper.class);
for (User user : userDAO.getUserList()) {
System.out.println(user);
}
// 关闭sqlSession对象
sqlSession.close();
}
目录结构:
如果mapper.xml文件想放到resources目录下,创建的目录路径必须为com/angenin/dao(注意:resources目录下是按照斜杆/进行目录分隔的),对应类的路径,把mapper文件放到这个目录下,如果只是用com.angenin.dao命名,其实只是一层目录,在target包下也可以看出生成的不是文件不是放在一起。
关于SqlSessionFactoryBuilder、SqlSessionFactory和SqlSession的注意事项:
mapper文件的命名空间namespace对应DAO接口的全限定类名,也是通过dao和mapper相关联,select标签的id对应DAO接口中方法的方法名,通过id相关联。
CRUD
UserMapper
public interface UserMapper {
// 获取所有数据
List<User> getUserList();
// 获取单个数据
User getOneUser(int id);
// 新增一个用户(不需要设置id,数据库自增)
void saveUser(User user);
// 修改一个用户(通过id)
void changeUser(User user);
// 删除一个用户
void removeUser(int id);
}
UserMapper.xml
<select id="getOneUser" parameterType="int" resultType="com.angenin.pojo.User">
select * from t_users where id=#{id};
</select>
<insert id="saveUser" parameterType="com.angenin.pojo.User">
insert into t_users(name,password)values(#{name},#{password});
</insert>
<update id="changeUser" parameterType="com.angenin.pojo.User">
update t_users set name=#{name},password=#{password} where id=#{id};
</update>
<delete id="removeUser" parameterType="int">
delete from t_users where id=#{id};
</delete>
测试:
// 测试查询单个用户
@Test
public void test02() {
// 获取sqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
// 获取UserDAO对象
UserMapper userDAO = sqlSession.getMapper(UserMapper.class);
System.out.println(userDAO.getOneUser(1));
// 关闭sqlSession对象
sqlSession.close();
}
// 测试新增单个用户
@Test
public void test03() {
// 获取sqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
// 获取UserDAO对象
UserMapper userDAO = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setName("test");
user.setPassword("test");
userDAO.saveUser(user);
// 提交事务
sqlSession.commit();
// 关闭sqlSession对象
sqlSession.close();
}
// 测试修改单个用户
@Test
public void test04() {
// 获取sqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
// 获取UserDAO对象
UserMapper userDAO = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setId(38);
user.setName("test2");
user.setPassword("test2");
userDAO.changeUser(user);
// 提交事务
sqlSession.commit();
// 关闭sqlSession对象
sqlSession.close();
}
// 测试删除单个用户
@Test
public void test05() {
// 获取sqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
// 获取UserDAO对象
UserMapper userDAO = sqlSession.getMapper(UserMapper.class);
userDAO.removeUser(38);
// 提交事务
sqlSession.commit();
// 关闭sqlSession对象
sqlSession.close();
}
注意:因为mybatis默认关闭自动提交,所以执行完后需要手动提交事务,如果程序执行没有报错,但是数据库中没变化,可能就是这个原因。
#{} 与 ${} 的区别
#{}:会预编译,把sql语句中#{}的位置用?代替,在执行JDBC时才进行赋值,防止sql注入。
${}:字符串拼接,直接把对应的值拼接到sql语句中,存在sql注入的问题,但在使用order by排序时,使用的是${}。
Map
当我们只需要修改一条记录中的某些字段时,使用map可以指定修改指定字段,不需要新建对象。
UserMapper
void changeUser2(Map<String, Object> map);
UserMapper.xml
<update id="changeUser2" parameterType="map">
update t_users set name=#{username},password=#{userpwd} where id=#{userid};
</update>
测试
@Test
public void test06() {
// 获取sqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
// 获取UserDAO对象
UserMapper userDAO = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
// 通过id修改密码
map.put("userid", 40);
map.put("userpwd", "pwd");
userDAO.changeUser2(map);
// 提交事务
sqlSession.commit();
// 关闭sqlSession对象
sqlSession.close();
}
mapper的#{}字段名通过map中的key找到value,如果类型是类,则是类属性名。
模糊查询
List<User> getUserLike();
<select id="getUserLike" parameterType="string" resultType="com.angenin.pojo.User">
select * from t_users where name like #{value};
</select>
@Test
public void test07() {
// 获取sqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
// 获取UserDAO对象
UserMapper userDAO = sqlSession.getMapper(UserMapper.class);
List<User> list = userDAO.getUserLike("%test%");
for (User user : list) {
System.out.println(user);
}
// 关闭sqlSession对象
sqlSession.close();
}
核心配置
引入配置文件(properties)
创建db.properties文件,把数据库的配置信息抽取出来
db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/angenin?useSSL=false&serverTimezone=Asia/Shanghai
db.username=root
db.password=123456
<!--引入外部配置文件-->
<properties resource="db.properties">
<!--可以在这里设置配置信息,如果内部和外部文件都设置了同一个属性名,以外部文件为准,如果这里不需要设置配置信息,使用单标签即可-->
<!--<property name="db.username" value="root"/>-->
<!--<property name="db.password" value="11111"/>-->
</properties>
<!-- 数据库连接 -->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${db.driver}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</dataSource>
</environment>
</environments>
注意:配置文件中的标签需要按照一定的顺序进行书写。
properties -> settings -> typeAliases -> typeHandlers -> objectFactory -> objectWrapperFactory -> reflectorFactory -> plugins -> environments -> databaseIdProvider -> mappers
设置(settings)
文档:https://mybatis.org/mybatis-3/zh/configuration.html#settings
一个配置完整的 settings 元素的示例如下:
mybatis-config.xml
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
settings有很多设置,最关键是下面三个:
- cacheEnabled:缓存,true为开启。
- lazyLoadingEnabled:懒加载,true为开启。
- logImpl:日志,可设置SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING。
别名(typeAliases)
在mapper文件中,要求我们使用全限定类名,这样会让我们的代码看起来很长,我们可以在mybatis的配置文件中设置类的别名,在mapper就可以不需要写全限定类名了。
<!--设置实体类的别名 -->
<typeAliases>
<typeAlias type="com.angenin.pojo.User" alias="User"/>
</typeAliases>
typeAliases属性下还有另一个标签package,是通过扫描指定包下的所有类自动设置别名,一般我们使用typeAlias标签,别名设置为首字母设置为大写,而包扫描默认为首字母小写,这样可以便于我们区分是用哪种方式设置的别名。
<typeAliases>
<package name="com.angenin.pojo"/>
</typeAliases>
我们可以在扫描包下的实体类上加上@Alias来自定义别名。
@Alias("u")
public class User implements Serializable {}
我们只要记住加了下划线的为基本类型,没加的为其包装类即可。
数据库环境(environments)
environments中可配置多个环境,通过default属性指定使用哪个环境。
<!-- default为test,所以是使用id为test的环境 -->
<environments default="test">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
...
</dataSource>
</environment>
<environment id="test">
<transactionManager type=""></transactionManager>
<dataSource type=""></dataSource>
</environment>
</environments>
事务管理器(transactionManager)
有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
- JDBC:使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
- MANAGED:这个配置几乎没做什么,了解即可。
数据源(dataSource)
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):
- UNPOOLED:这个数据源的实现会每次请求时打开和关闭连接,即没有池。
- POOLED:利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。
- JNDI:为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
具体配置参数可以自行查看文档:
https://mybatis.org/mybatis-3/zh/configuration.html#environments
映射器(mappers)
mappers有mapper和package两种方式,而mapper有三种方式。
不推荐使用url的方式。
除了resource,其他方式都需要接口和mapper文件在同个包下,并且需要同名
生命周期和作用域
不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
-
SqlSessionFactoryBuilder
可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它。 -
SqlSessionFactory
一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例,使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。。 -
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域,每个请求对应一个新的SqlSession,并且使用完后立即销毁。
解决数据库和Java实体类属性名不一致的问题
我们把User类的属性名修改为password改为pwd,这样我们实体类属性名和数据库对应的表的字段名就都不一致了,不一致会导致赋值不成功,对应的属性为null。
解决办法:
-
SQL语句使用as
<select id="getUserList" resultType="User"> select password as pwd from t_users; </select>
-
封装为结果集映射(ResultMap)
只需要封装不一致的属性即可。<resultMap id="UserMap" type="com.angenin.pojo.User"> <!-- column对应数据库表的字段名,property对应java实体类的属性名 --> <result property="pwd" column="password"/> </resultMap> <select id="getUserList" resultMap="UserMap"> select * from t_users; </select>
关于ResultMap更具体的用法会在下面写出。
日志
settings的 logImpl,可设置:
- SLF4J
- LOG4J【掌握】
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING【掌握】
- NO_LOGGING
-
使用STDOUT_LOGGING
内置,可直接使用。<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
-
使用LOG4J
- 需要导入log4j的jar包。
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
- 创建log4j的配置文件log4j.properties
# 配置根 log4j.rootLogger=debug,console # 日志输出到控制台显示 log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.Target=System.out log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
需要更详细的配置自行百度。
- 将日志设置log4j
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
limit分页
减少数据的处理量。
UserMapper
// 分页
List<User> getUserLimit(Map<String, Integer> map);
UserMapper.xml
<select id="getUserLimit" parameterType="map" resultMap="UserMap">
select * from t_users limit #{startIndex},#{pageSize};
</select>
测试
@Test
public void test08() {
// 获取sqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
// 获取UserDAO对象
UserMapper userDAO = sqlSession.getMapper(UserMapper.class);
Map<String, Integer> map = new HashMap<>();
map.put("startIndex", 2);
map.put("pageSize", 2);
List<User> list = userDAO.getUserLimit(map);
for (User user : list) {
System.out.println(user);
}
// 关闭sqlSession对象
sqlSession.close();
}
关于limit第二个参数为-1的问题:为第二个参数为-1时,会从第一个参数开始一直查找到最后,mysql认为它是bug,现在已经修复了,所以现在mysql高版本中limit的第二个参数不能小于0。
分页插件:PageHelper
分页插件底层也是使用limit,有兴趣的可以自行参照文档学习使用。
注解开发
把UserMapper.xml移出dao包,然后在UserMapper接口的getUserList方法上加上@Select注解,在注解里写上sql语句,增删改用法类似。
UserMapper
public interface UserMapper {
// 查询所有
@Select("select * from t_users")
List<User> getUserList();
// 查询单个
@Select("select * from t_users where id=#{id}")
User getOneUser(@Param("id") int id);
// 新增
@Insert("insert into t_users(name,password)values(#{name},#{pwd})")
void saveUser(User user);
// 修改
@Update("update t_users set name=#{name}, password=#{pwd} where id=#{id}")
void changeUser(User user);
// 删除
@Delete("delete from t_users where id=#{id}")
void removeUser(@Param("id") int id);
}
关于@Param:如果有@Param,#{}中属性名对应@Param指定的属性名,基本类型或者String类型的参数,需要加上,指定属性名,引用类型不需要加,只有一个基本类型或String的参数时也可以不加。
绑定接口
<mappers>
<!-- 目录按斜杠/分隔 -->
<!-- <mapper resource="com/angenin/dao/*Mapper.xml"/>-->
<mapper class="com.angenin.dao.UserMapper"/>
</mappers>
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
底层:动态代理 + 反射
resultMap处理多对一
创建教师表和学生表(学生表通过外键关联教师表)
create table teacher(
`id` int primary key auto_increment,
`name` varchar(15) default null
);
insert into teacher(`name`)values("李老师");
create table student(
`id` int primary key auto_increment,
`name` varchar(15) default null,
`tid` int,
foreign key(tid) references teacher(id)
);
insert into student(`name`,`tid`)values("小明",1);
insert into student(`name`,`tid`)values("小红",1);
insert into student(`name`,`tid`)values("小刚",1);
创建学生和教师实体类
@Data
public class Student {
private int id;
private String name;
private Teacher teacher;
}
@Data
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
按照查询嵌套处理
StudentMapper
public interface StudentMapper {
// 获取学生表中的所有学生,并显示其对应的教师名
List<Student> getStudents();
}
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="com.angenin.dao.StudentMapper">
<resultMap id="StudentTeacher" type="Student">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!--复杂属性需要单独处理,association:对象,collection:集合-->
<association property="teacher" javaType="Teacher" column="tid" select="getTeacher"/>
</resultMap>
<select id="getStudents" resultMap="StudentTeacher">
select * from student;
</select>
<!--报红没关系-->
<select id="getTeacher" resultType="Teacher">
select * from teacher where id=#{tid};
</select>
</mapper>
mybatis-config.xml
<typeAliases>
<package name="com.angenin.pojo"/>
</typeAliases>
<mappers>
<mapper resource="com/angenin/dao/StudentMapper.xml"/>
</mappers>
测试
@Test
public void test09() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper studentDAO = sqlSession.getMapper(StudentMapper.class);
List<Student> students = studentDAO.getStudents();
for (Student student : students) {
System.out.println(student);
}
sqlSession.close();
}
按照结果嵌套处理
StudentMapper
List<Student> getStudents2();
StudentMapper.xml
<resultMap id="StudentTeacher2" type="Student">
<!--数据表字段名对应as别名-->
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<!--第一种写法-->
<!--复杂属性需要单独处理,association:对象,collection:集合-->
<!-- <association property="teacher" javaType="Teacher">-->
<!-- <result property="name" column="tname"/>-->
<!-- </association>-->
<!--第二种写法-->
<result property="teacher.name" column="tname"/>
</resultMap>
<select id="getStudents2" resultMap="StudentTeacher2">
select s.id as sid,s.name as sname,t.name as tname
from student as s, teacher as t
where s.tid=t.id;
</select>
官方也推荐用按照结果嵌套处理的方式进行处理。
resultMap处理一对多
TeacherMapper
public interface TeacherMapper {
// 通过id获取指定教师,及其对应的所有学生
Teacher getTeacher(@Param("tid")int id);
}
TeacherMapper.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="com.angenin.dao.TeacherMapper">
<!--按照结果嵌套查询-->
<resultMap id="TeacherStudent" type="Teacher">
<id property="id" column="tid"/>
<result property="name" column="tname"/>
<!--复杂属性需要单独处理,association:对象,collection:集合-->
<!--ofType:指定list泛型的属性,javaType指定类型-->
<collection property="students" ofType="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>
<select id="getTeacher" resultMap="TeacherStudent">
select t.id as tid, t.name as tname, s.id as sid, s.name as sname
from teacher as t, student as s
where t.id=#{tid} and t.id=s.tid;
</select>
</mapper>
测试
@Test
public void test10() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeacherMapper teacherDAO = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = teacherDAO.getTeacher(1);
System.out.println(teacher);
sqlSession.close();
}
一对多,多对一小结
association:关联(多对一)
collection:集合(一对多)
javaType:指定对象类型
ofType:指定集合的泛型类型
动态SQL
官方文档:https://mybatis.org/mybatis-3/zh/dynamic-sql.html
IF
如果有name参数,通过单独查找指定用户,否则查找全部用户。
StudentMapper
List<Student> queryStudentIF(Map<String, Object> map);
StudentMapper.xml
<select id="queryStudentIF" parameterType="map" resultType="Student">
select * from student where 1=1
<if test="username != null">
and name=#{username}
</if>
</select>
1=1是为了where的合法性,如果where放到if标签里的话,如果有多个判断呢,一条sql语句通常只有一个where,所以放在外面,用1=1保证where合法,这样可以保证我们可以写多个if标签。
测试
@Test
public void test11() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper studentDAO = sqlSession.getMapper(StudentMapper.class);
Map<String, Object> map = new HashMap<>();
// 不传参,查找全部
// 传参,单独查找
//map.put("username", "小刚");
List<Student> students = studentDAO.queryStudentIF(map);
for (Student student : students) {
System.out.println(student);
}
sqlSession.close();
}
choose、when、otherwise
choose结构与Java中的switch类似,when对应Java的case,otherwise对应Java的default。
<select id="queryStudentIF" parameterType="map" resultType="Student">
select * from student where
<choose>
<when test="sid != null">
and id=#{sid}
</when>
<when test="username != null">
and name=#{username}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</select>
when标签有一个返回即停止,不会再去判断其他when,如果所有的when都不符合,执行otherwise。
where
看下面的代码,这个代码存在一些问题,如果if都不符合,那么where语句后会没有判断条件,或者如果sid为空,但是username不为空,那么会变成... where and ...
。
<select id="queryStudentIF" parameterType="map" resultType="Student">
select * from student where
<if test="sid != null">
id=#{sid}
</if>
<if test="username != null">
and name=#{username}
</if>
</select>
解决办法可以用上面的choose,也可以用where标签,where标签会在子标签有返回内容的情况下才会插入where关键字,并且where会判断如果拼接的第一条语句开头为and或or,会自动去除。
<select id="queryStudentIF" parameterType="map" resultType="Student">
select * from student
<where>
<if test="sid != null">
id=#{sid}
</if>
<if test="username != null">
and name=#{username}
</if>
</where>
</select>
建议多使用where标签替代where关键字,因为where标签只有子标签有返回结果时才生效,省去一些if判断。
trim
如果对where不满意,可以使用trim进行自定义。
<!--这个trim作用与where标签一样,可以自行修改-->
<trim prefix="where" prefixOverrides="and |or ">
<if test="sid != null">
id=#{sid}
</if>
<if test="username != null">
and name=#{username}
</if>
</trim>
prefix:开头添加的内容,如同where标签添加的where关键字。
prefixOverrides:第一个拼接语句开头如果存在就要移除的内容,and和or后面的空格是必须的,因为sql语句中and和or关键字后就有空格。
suffix:结尾添加的内容。
suffixOverrides:最后一个拼接语句末尾如果存在就要移除的内容。
set
set标签对应sql语句update中的set关键字,用于修改数据时,根据要修改内容进行动态更新。
StudentMapper
void changeStudentSET(Map<String, Object> map);
StudentMapper.xml
<update id="changeStudentSET" parameterType="map">
update student
<set>
<!--多个if判断,因为建的表字段少,所以这里就只用一个-->
<if test="username != null">name=#{username},</if>
</set>
where id=#{sid};
</update>
测试
@Test
public void test12() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper studentDAO = sqlSession.getMapper(StudentMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("sid", "2");
map.put("username", "小兰");
studentDAO.changeStudentSET(map);
sqlSession.commit();
sqlSession.close();
}
set标签会去除拼接的sql语句最后多余的逗号,。
使用trim标签模拟set标签的功能:
<update id="changeStudentSET" parameterType="map">
update student
<trim prefix="set" suffixOverrides=",">
<if test="username != null">name=#{username},</if>
</trim>
where id=#{sid};
</update>
foreach
使用foreach动态拼接成select * from student where 1=1 and (id=1 or id=2);
<select id="queryStudentFOREACH" parameterType="map" resultType="Student">
select * from student
<where>
<foreach collection="ids" item="item" index="index" open="and (" separator=" or " close=")">
id=#{item}
</foreach>
</where>
</select>
collection:遍历的集合
item:当前遍历到的元素
index:当前遍历到的下标
open:拼接到sql语句的开头的片段。
separator:遍历元素中间添加的分隔符。
close:拼接到sql语句的末尾的片段。
测试
@Test
public void test13() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper studentDAO = sqlSession.getMapper(StudentMapper.class);
Map<String, Object> map = new HashMap<>();
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
map.put("ids", list);
List<Student> students = studentDAO.queryStudentFOREACH(map);
students.forEach(System.out::println);
sqlSession.close();
}
SQL片段
抽取重复使用的sql片段,与java中抽取重复代码封装成函数一样,减少代码量,方便复用,但也要注意,抽取的sql片段尽量简单,最好只放判断语句if或choose,如果太复杂使用的次数也相对较少。
<sql id="if-sid-username">
<if test="sid != null">
id=#{sid}
</if>
<if test="username != null">
and name=#{username}
</if>
</sql>
<select id="queryStudentIF" parameterType="map" resultType="Student">
select * from student
<where>
<include refid="if-sid-username"/>
</where>
</select>
缓存
官方文档:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache
缓存就是把查询到的数据存放到内存里,下次再进行相同的查询就不用通过数据库,直接从缓存(内存)中获取数据,这样可以减少跟数据库的交互,减少系统开销,提高效率,经常查询并且不经常修改的数据最合适用缓存。
Mybatis有一级缓存和二级缓存,默认开启一级缓存。
所有查询操作都会被保存到缓存中,并且所有的增删改操作都会刷新缓存,缓存不会自动刷新(定时刷新)。
一级缓存
一级缓存:同个SqlSession中,多次进行同一个的查询操作,只有第一次操作需要与数据库交互,并保存到缓存中,接下来的查询操作会直接从缓存中获取数据,不会与数据库交互,具体可以通过日志查看。
使用sqlSession.clearCache();
也可以清空SqlSession的缓存。
一级缓存就是一个Map。
二级缓存
在mapper文件中添加<cache/>
,代表此mapper文件使用二级缓存,默认的清除策略为LRU(移除最长时间没使用的对象),cache标签也可以自定义属性。
mybatis-config.xml
<settings>
<!--表明我们要开启全局缓存,不加这个配置也可以,因为默认也是开启的,不过写出来方便别人知道我们用到了全局缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
使用缓存时,每个实体类都需要序列化(实现Serializable接口),否则会报错NotSerializableException,不想序列化的话,在cache标签设置readOnly属性为true即可,不过建议实体类都序列化。
每个mapper为都有一个二级缓存。每个SqlSession的查询操作后的数据会保存到一级缓存中,当mapper开启二级缓存后,当它的SqlSession提交或者关闭后,才会把一级缓存中的内容保存到二级缓存里,不关闭不会保存到二级缓存。
-
没开启缓存时
同个SqlSession同个mapper使用一级缓存
不同个SqlSession同个mapper不能使用缓存 -
开启缓存后
同个SqlSession同个mapper先使用二级缓存,如果二级缓存中不存在,再使用一级缓存。
不同个SqlSession同个mapper使用二级缓存
不同mapper不能使用缓存。
在开启二级缓存后,mapper文件中的select查询可以把useCache属性设置为false,表示当前查询不使用缓存,在增删改操作中可以把flushCache属性设置为false,表示当前操作不刷新缓存。
自定义缓存
实现Cache接口,然后在cache标签中type属性引用即可,目前大多数都是使用redis作为缓存。