Mybatis
中文文档: https://mybatis.org/mybatis-3/zh/index.html 这里面包含了Mybatis从入门到配置的所有内容,方便后面查询。
一.概念
- MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
- 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 实体类 【Plain Old Java Objects,普通的 Java对象】映射成数据库中的记录。
持久化: 持久化是将程序数据在持久状态和瞬时状态间转换的机制。即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中、XML数据文件中等等。
JDBC就是一种持久化机制。文件IO也是一种持久化机制。
持久层: 用来操作数据库。将内存中的数据保存到磁盘上加以固化,而持久化的实现过程则大多通过各种关系数据库来完成。
二.入门
1.新建maven普通项目,在pom.xml中导入Mybatis等包依赖,并添加资源构建(因为后面有些xml文件等资源是放在java文件夹以下的,默认是不能导出,以此防止编译错误找不到资源)
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- 快速生成javabean的set,get方法
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</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>
2.在resources文件夹下新建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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/smbms?useSSL=false&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>
3.在java文件夹下新建MybatisUtils工具类,获取SqlSession的一个对象
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//固定写法,Mybatis文档也是写的
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//获取SqlSession连接
public static SqlSession getSession(){
return sqlSessionFactory.openSession();
}
}
4.在同一个文件夹(如mapper文件夹下)创建Mapper接口(对应之前的Dao接口)以及对应的xml配置文件(对应之前的DaoImpl实现类)
xml配置文件中我们只要注意namespace为Mapper接口的全类名,id为Mapper接口的方法名,resultType为该方法的返回类型(全类名)
我们只用关心我们写的sql语句,不用关心如何获取数据库连接对象,sql执行对象,结果集对象以及关闭资源等等(这些工作都交给SqlSession去做)
public interface UserMapper {
List<User> getUsers();
}
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.liqingfeng.mapper.UserMapper">
<select id="getUsers" resultType="com.liqingfeng.pojo.User">
select * from smbms_user
</select>
</mapper>
并在核心配置文件mybatis-config.xml中加入映射
<mappers>
<mapper resource="com/liqingfeng/mapper/UserMapper.xml"/>
</mappers>
User类是表user的java bean 实体类
5.测试(Junit)
public class UserMapperTest {
@Test
public void getUsers(){
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);//就像以前的:Dao user = new DaoImpl()一样
List<User> userList = mapper.getUsers();
for (User user : userList) {
System.out.println(user);
}
}
}
三.增删改查
UserMapper接口
public interface UserMapper {
List<User> getUsers();
User getUserById(@Param("uid") int id);
User getUserByMap(Map map);
int addUser(User user);
int deleteUser(User user);
int updateUser(User user);
}
UserMapper.xml配置文件
- id:方法名
- resultType:方法返回值类型
- parameterType:方法参数类型
- 增删改不用写resultType(因为返回值都是int)
- #{ }里的名称是根据parameterType来的。比如:1.parameterType为User类型(引用类型),则#{ }里的名称为User的属性名(属性即get/set方法去掉get/set首字母小写所得的字符串);2.parameterType为普通类型(int、char、包括String类型),则#{ }里的名称为方法参数上@Param( )里的名称;3.parameterType为Map类,则#{ }里的名称为map集合的键名。
sql中关于使用#{ }而不是使用 ${ }来取值的问题?
#{ }能防止sql注入, ${ }不能防止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="com.liqingfeng.mapper.UserMapper">
<select id="getUsers" resultType="com.liqingfeng.pojo.User">
select * from smbms_user
</select>
<select id="getUserById" resultType="com.liqingfeng.pojo.User" parameterType="int">
select * from smbms_user where id = #{uid}
</select>
<select id="getUserByMap" resultType="com.liqingfeng.pojo.User" parameterType="map">
select * from smbms_user where id = #{uid} and userName = #{uname}
</select>
<insert id="addUser" parameterType="com.liqingfeng.pojo.User">
insert into smbms_user(id, userName, userPassword) values (#{id},#{userName},#{userPassword})
</insert>
<delete id="deleteUser" parameterType="com.liqingfeng.pojo.User">
delete from smbms_user where id = #{id};
</delete>
<update id="updateUser" parameterType="com.liqingfeng.pojo.User">
update smbms_user set userName = #{userName} where id = #{id};
</update>
</mapper>
测试:
public class UserMapperTest {
@Test
public void getUsers(){
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);//就像以前的:Dao user = new DaoImpl()一样
List<User> userList = mapper.getUsers();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();//切记关闭资源
}
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);//就像以前的:Dao user = new DaoImpl()一样
User user = mapper.getUserById(1);
System.out.println(user);
sqlSession.close();//切记关闭资源
}
@Test
public void getUserByMap(){
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);//就像以前的:Dao user = new DaoImpl()一样
HashMap<String, Object> map = new HashMap<>();
map.put("uid", 1);
map.put("uname", "文人");
User user = mapper.getUserByMap(map);
System.out.println(user);
sqlSession.close();//切记关闭资源
}
@Test
public void addUser(){
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);//就像以前的:Dao user = new DaoImpl()一样
User user = new User();
user.setId(18);
user.setUserName("啦啦啦");
user.setUserPassword("111111");
int num = mapper.addUser(user); //返回受影响行数
System.out.println(num);
sqlSession.commit();//增删改需要提交事务,Mybatis默认关闭自动提交
sqlSession.close();//切记关闭资源
}
@Test
public void deleteUser(){
SqlSession sqlSession = MybatisUtils.getSession();
User user = new User();
user.setId(18);
int num = sqlSession.getMapper(UserMapper.class).deleteUser(user);
System.out.println(num);
sqlSession.commit();
sqlSession.close();
}
@Test
public void updateUser(){
SqlSession sqlSession= MybatisUtils.getSession();
User user = new User();
user.setId(18);
user.setUserName("lalala");
int num = sqlSession.getMapper(UserMapper.class).updateUser(user);
System.out.println(num);
sqlSession.commit();
sqlSession.close();
}
}
模糊查询:
1.代码层面实现:传入参数 "%…%"
UserMapper接口新加方法
List<User> getUserLike(@Param("value") String v);
UserMapper.xml:
<select id="getUserLike" resultType="com.liqingfeng.pojo.User" parameterType="String">
select * from smbms_user where userName like #{value}
</select>
测试:
@Test
public void getUserLike(){
SqlSession sqlSession= MybatisUtils.getSession();
List<User> users = sqlSession.getMapper(UserMapper.class).getUserLike("%文%");
for (User user : users) {
System.out.println(user);
}
sqlSession.commit();
sqlSession.close();
}
2.sql层面实现:代码层传入模糊字符串,sql层面实现拼接 “%”#{value}"%"
UserMapper.xml:
<select id="getUserLike" resultType="com.liqingfeng.pojo.User" parameterType="String">
select * from smbms_user where userName like "%"#{value}"%"
</select>
测试:
@Test
public void getUserLike(){
SqlSession sqlSession= MybatisUtils.getSession();
List<User> users = sqlSession.getMapper(UserMapper.class).getUserLike("文");
for (User user : users) {
System.out.println(user);
}
sqlSession.commit();
sqlSession.close();
}
分页查询:
UserMapper接口新加方法:
List<User> getUserByLimit(Map map);
UserMapper.xml::
<select id="getUserByLimit" resultType="User" parameterType="map">
select * from user limit #{start},#{pageSize}
</select>
测试:
@Test
public void getUserByLimit(){
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Integer> map = new HashMap<>();
map.put("start",0); //起始索引
map.put("pageSize",2); //每页大小
List<User> users = mapper.getUserByLimit(map);
for (User user : users) {
System.out.println(user);
}
sqlSession.close();//切记关闭资源
}
四.配置
https://mybatis.org/mybatis-3/zh/configuration.html
都在mybatis-config.xml核心配置文件配置这些属性
1.properties属性配置
作用:引入外部配置文件
resources文件夹下的db.properties
driver=com.mysql.jdbc.Driver
url=url=jdbc:mysql://localhost:3306/shool?useSSL=true&useUnicode=true&characterEncoding=utf8
name=root
pwd=123456
在mybatis-config.xml中导入外部配置文件
<!--导入外部配置文件(resource写classes下的路径)-->
<properties resource="db.properties">
</properties>
在其他地方比如环境的property中,可以使用value=¥{ }来取properties 导入的配置文件中属性的值
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${name}"/>
<property name="password" value="${pwd}"/>
</dataSource>
</environment>
如果在properties 里添加property 又对pwd属性进行了配置,则优先级是db.properties最大(如果db.properties没有pwd,则property 里的pwd配置生效),了解即可。
<properties resource="db.properties">
<property name="pwd" value="123456"/>
</properties>
2.settings属性配置
作用:设置mybatis的一些功能,如开启二级缓存,日志等
下面列出3个常用的设置功能
cacheEnabled: 开启或关闭二级缓存
lazyLoadingEnabled:开启或关闭延迟加载
logImpl:开启或关闭日志
在mybatis-config.xml中设置开启日志(STDOUT_LOGGING:控制台打印日志信息)
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
3.typeAliases属性配置
作用:给java类型起一个短的名字,简化书写。
在mybatis-config.xml中添加User类别名为User
<!--类的别名-->
<typeAliases>
<typeAlias type="com.liqingfeng.pojo.User" alias="User"/>
</typeAliases>
UserMapper.xml中resultType就不用指定全类名了,直接使用别名就ok
<select id="getUsers" resultType="User">
select * from `user`
</select>
也可以给包取别名,这样包中的所有类,在没有注解的情况下,会使用类名的首字母小写的类名来作为它的别名(其实别名不区分大小写,小写只是规范)。 比如 com.liqingfeng.pojo.User 的别名为 author;若有注解,则别名为其注解值。见下面的例子:
<typeAliases>
<package name="com.liqingfeng.pojo"/>
</typeAliases>
在没有注解的情况下可以在UserMapper.xml中resultType取别名(resultType=“user”)即可。但若有注解@Alias(…),则别名为其注解值(即resultType=“users”)
@Alias("users")
public class User{
...
}
4.environments属性配置
作用:Mybatis可以配置多套环境,以适应不同需求,但一个SqlSessionFactory只能选择一种环境。
default="development"可以选择一套环境,id是该套环境的唯一标识,用来被选择的(如default=“test”,则选择id="test"的环境)
mybatis-config.xml中配置多套环境如下:
<environments default="test">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/shool?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/shool?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
事务管理器transactionManager默认选择JDBC,默认关闭自动提交,需手动提交事务
数据源dataSource默认选择POOLED(表示有数据库连接池,至于选择的数据库连接池不是c3p0也不是德鲁伊,是一个PooledDataSource(可以看成是mybatis自己实现的一个连接池))
5.mappers属性配置
作用:mappers(映射器),给mapper.xml(Mapper接口的配置文件)做映射的,告诉 MyBatis 到哪里去找到sql语句。
mybatis-config.xml中mappers映射如下:
方法一:直接指定(会映射该mapper.xml和mapper接口方法上的sql注解)
<mappers>
<mapper resource="com/liqingfeng/mapper/UserMapper.xml"/>
</mappers>
方法二:使用class文件绑定映射(会映射该mapper.xml和mapper接口方法上的sql注解)
<mappers>
<mapper class="com.liqingfeng.mapper.UserMapper"/>
</mappers>
方法三:使用包扫描绑定映射(会映射该包下所有的mapper.xml文件和mapper接口方法上的sql注解)
<mappers>
<package name="com.liqingfeng.mapper">
</mappers>
方法二和方法三注意点:mapper接口和对应mapper.xml必须同名,且必须在同一包下。如上面的UserMapper.xml和UserMapper同名且在同一包下,所以方法二和方法三都可以映射成功。
五.Mybtis中对象的作用域和生命周期
1.SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder一旦创建了 SqlSessionFactory对象就不再需要它了。所以最佳作用域是局部方法变量。
2.SqlSessionFactory对象
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例(把SqlSessionFactory看成是数据库连接池对象,要在程序运行期间一直存在),所以最佳作用域是全局变量。
3.SqlSession对象
每个线程都应该有它自己的 SqlSession 实例。SqlSession的实例不是线程安全的,因此是不能被共享的(把SqlSession看成是连接池中的一个个数据库连接对象,用完就放回池中),所以它的最佳的作用域是局部方法变量。
六.resultMap结果映射
在没有显示做resultMap结果映射时,select 语句中的列名会对应实体类的属性名,没有对应上的结果为null。
数据库列名:
实体类的属性名(get/set方法去掉get/set,首字母小写的字符串),这里将password改为pwd。(对应的get/set也要改,因为他是按照属性来映射的。如果还是get/setPassword,那么属性名就还是password,查询的列名还是能对应属性)
UserMapper.xml中的sql语句:
<select id="getUsers" resultType="User">
select * from `user`
</select>
测试结果如下:
由于查询结果的列名为password,而属性名为pwd,所以结果不能映射起来,故pwd=null
解决查询结果列名与属性名不一致方法
方法1:给列名取别名
UserMapper.xml中的sql语句:select id,name,password pwd from user
,结果如下:
由于列名为 pwd,而属性名也为pwd,所以结果可以映射起来,故实体类对象属性pwd有值
方法2:通过resultMap做结果映射
resultType只能做简单的结果映射,resultMap可以做复杂的结果映射
type="User"为需要映射的实体类,这里用了类的别名(没有用全类名),resultMap="UserMap"对应resultMap的id=“UserMap”
<!--结果映射-->
<resultMap id="UserMap" type="User">
<!--property对应实体类的属性,column对应查询结果的列名-->
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="pwd" column="password"/>
</resultMap>
<select id="getUsers" resultMap="UserMap">
select * from `user`
</select>
七.日志
日志是用来打印信息的,在我们遇到错误的时候有很大的帮助(比如查看sql语句是否执行正确)
mybatis文档关于日志的讲解:https://mybatis.org/mybatis-3/zh/logging.html
1.STDOUT_LOGGING
settings属性配置中讲过该日志的实现,这里不再赘诉。
2. LOG4J
LOG4J是Apache的一个开放源代码项目,通过使用LOG4J:
- 我们可以控制日志信息输送的目的地是控制台、文件等;
- 我们也可以控制每一条日志的输出格式;
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
- 可以通过一个配置文件来灵活地进行LOG4J配置,而不需要修改应用的代码
配置:
步骤1.导入LOG4J依赖jar
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
步骤2.resources文件夹下创建log4j.properties
,内容如下
log4j.appender.file.File=./log/log4j.log : 表示日志输出信息保存的路径
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n : 表示日志输出信息保存的文件中时间展示的格式
log4j.logger=DEBUG : 表示任何地方的操作执行都将记录日志
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/log4j.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
步骤3:在mybatis-config.xml中设置开启日志STDOUT_LOGGING(这一步省略日志也可以输出,但是还是习惯写上)
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
关闭日志
NO_LOGGING:表示不使用日志输出信息
<settings>
<setting name="logImpl" value="NO_LOGGING"/>
</settings>
像sout一样使用日志
也就是像sout一样,利用日志打印一些调试信息,有3个级别
@Test
public void testLog4j(){
Logger logger = Logger.getLogger(UserMapper.class);//对哪个类进行调试信息打印
logger.info("info级别的日志调试");
logger.debug("debug级别的日志调试");
logger.error("error级别的日志调试");
}
结果:
八.注解开发
原理:底层利用反射和动态代理,获取类的class对象,从而就能获取该类的注解信息。
使用注解开发可以省去在mapper.xml中写sql的麻烦(即不用写mapper.xml),因为注解直接加在方法上,更加直观。但是,注解开发的结果映射只能做到简单的映射,如果是复杂的结果映射,还是需要在mapper.xml中利用resultMap。
步骤1:在UserMapper接口中给对应方法加上sql注解
@Select("select * from `user`")
List<User> selectUsers();
步骤2:在mybatis-config.xml核心配置文件中加上映射配置(这里使用class绑定sql映射)
<mappers>
<mapper class="com.liqingfeng.mapper.UserMapper"/>
</mappers>
测试:
@Test
public void selectUsers(){
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectUsers();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
九.高级结果映射(关联-集合:多对一-一对多)
需要用的表,student表的tid是外键,参照teacher表的id
1.多对一(关联)
需要映射的结果中有一个实体类属性是引用类型(java类对象),这时需要用到关联(association)
实体类:
public class Teacher {
private int id;
private String name;
public class Student {
private int id;
private String name;
private Teacher teacher; //多对一,多个学生对应一个老师
如果不做关联映射对象,我们在查询Student的信息时,就会发现teacher = null(原因就是结果的列名对应不上Student 的属性名teacher)
比如我想查询所有学生以及对应老师的姓名,怎么做?
StudentMapper接口添加查询方法
List<Student> getStudents();
StudentMapper.xml写上sql以及需要映射的关系(这里注意,association 使用javaType指定属性的类型)
<resultMap id="StudentMap" type="com.liqingfeng.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<!--复杂的属性(这里属性类型为java类),我们需要单独处理-->
<!--javaType:指定该属性的Java类型-->
<association property="teacher" javaType="com.liqingfeng.pojo.Teacher">
<!--结果的tname列名映射到Teacher的name属性-->
<result property="name" column="tname"/>
</association>
</resultMap>
<select id="getStudents" resultMap="StudentMap">
select s.id sid, s.name sname , t.name tname
from student s,teacher t
where s.tid = t.id
</select>
测试及其结果:
@Test
public void getStudents(){
SqlSession sqlSession = MybatisUtils.getSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.getStudents();
for (Student student : students) {
System.out.println(student);
}
sqlSession.close();
}
2.一对多(集合)
需要映射的结果中有一个实体类属性是集合,这时需要用到集合(collection)
实体类:
public class Teacher {
private int id;
private String name;
private List<Student> students; //一对多,一个老师有多个学生
public class Student {
private int id;
private String name;
如果不做集合映射对象,我们在查询Teacher 的信息时,就会发现students= null(原因就是结果的列名对应不上Teacher 的属性名students)
比如我想查询指定老师id下的老师和学生对应信息,怎么做?
TeacherMapper接口添加查询方法
Teacher getTeacherById(@Param("id") int id);
TeacherMapper.xml写上sql以及需要映射的关系(这里注意,collection 使用ofType指定属性的类型)
<resultMap id="TeacherMap" type="com.liqingfeng.pojo.Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--复杂的属性(这里属性类型集合),我们需要单独处理-->
<!--ofType:指定该属性的Java类型-->
<collection property="students" ofType="com.liqingfeng.pojo.Student">
<!--结果的sid、sname列名分别映射到Student的id、name属性-->
<result property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>
<select id="getTeacherById" resultMap="TeacherMap" parameterType="int">
select s.id sid, s.name sname , t.name tname, t.id tid
from student s,teacher t
where s.tid = t.id and t.id=#{id}
</select>
测试及其结果:
@Test
public void getTeacherById(){
SqlSession sqlSession = MybatisUtils.getSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacherById(1);
System.out.println(teacher);
sqlSession.close();
}
十.动态SQL
https://mybatis.org/mybatis-3/zh/dynamic-sql.html
动态SQL:根据不同条件生成不同的SQL语句
1.if,where,set
根据条件为真时动态拼接SQL,if和where一般一起使用,因为拼接SQL大部分情况都需要where条件。而bybatis的where 元素只会在子元素返回任何内容的情况下才插入 where 子句。而且,若子句的开头为 “and” 或 “or”,where 元素也会将它们去除。
比如下面upwd 不为空,则语句拼接是将自动删除and
//UserMapper接口添加方法
List<User> getUserByIf(Map map);
UserMapper.xml
<select id="getUserByIf" resultType="User" parameterType="map">
select * from `user`
<where>
<if test="uname != null">
name = #{uname}
</if>
<if test="upwd != null">
and password = #{upwd}
</if>
</where>
</select>
测试:(根据传递的参数uname或upwd,在UserMapper.xml判断uname或upwd不为null时就动态拼接对应的SQL语句)
@Test
public void getUserByIf(){
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<>();
map.put("uname","王五");
// map.put("upwd","5201314");
List<User> users = mapper.getUserByIf(map);
for (User user : users) {
System.out.println(user);
}
sqlSession.close();//切记关闭资源
}
而set元素是在进行update操作的时候使用,因为动态拼接SQL难免会有逗号,多一个或则少一个逗号都不行。而set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号。
比如下面的当传入password不为空时,则语句会被拼接,那么后面的逗号就会被自动删除
<update id="updateUser" parameterType="com.liqingfeng.pojo.User">
update user
<set>
<if test="name != null">
name = #{name},
</if>
<if test="password != null">
password = #{password},
</if>
</set>
where id = #{id}
</update>
2.choose,when otherwise
choose,when otherwise就像switch语句,满足一个case就跳出,否则执行default。
//UserMapper接口添加方法
List<User> getUserByChoose(Map map);
UserMapper.xml
<select id="getUserByChoose" resultType="User" parameterType="map">
select * from `user`
<where>
<choose>
<when test="uname != null">
name = #{uname}
</when>
<when test="upwd != null">
and password = #{upwd}
</when>
<!--上面的都不成立,执行otherwise-->
<otherwise>
id = #{uid}
</otherwise>
</choose>
</where>
</select>
测试:(当三个都传值,根据switch特性只会执行第一个case就跳出语句)
@Test
public void getUserByChoose(){
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<>();
map.put("uid",2);
map.put("uname","lalala");
map.put("upwd","111111");
List<User> users = mapper.getUserByChoose(map);
for (User user : users) {
System.out.println(user);
}
sqlSession.close();//切记关闭资源
}
3.foreach
需求:对集合进行遍历(尤其是在构建 IN 条件语句的时候)
collection:传入参数中集合的名字,记住是集合类型
item:每次遍历生成的对象
open:开始遍历时拼接的字符串
close:结束遍历时拼接的字符串
separator:遍历对象item之间需要拼接的字符串
<select id="queryBlogForeach" parameterType="map" resultType="Blog">
select * from user
<where>
<foreach collection="ids" item="itemid" open="and (" close=")" separator="or">
id = #{itemid}
</foreach>
</where>
</select>
测试:(当传入的集合(list为例)为{1,2,3)时,其sql语句select * from user where (id=1 or id=2 or id=3)
@Test
public void getUserByForeach(){
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<>();
ArrayList<Integer> arrayId = new ArrayList<>();
arrayId.add(1);
arrayId.add(2);
arrayId.add(3);
map.put("ids",arrayId);
List<User> users = mapper.getUserByForeach(map);
for (User user : users) {
System.out.println(user);
}
sqlSession.close();//切记关闭资源
}
4.sql片段
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
- 最好基于 单表来定义 sql 片段,提高片段的可重用性
- 在 sql 片段中不要包括 where
提取SQL片段:
<sql id="if-uname -password">
<if test="uname != null">
name = #{uname}
</if>
<if test="upwd != null">
and password = #{upwd}
</if>
</sql>
引用SQL片段:
<select id="getUserByIf" resultType="User" parameterType="map">
select * from user
<where>
<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
<include refid="if-uname -password"></include>
<!-- 在这里还可以引用其他的 sql 片段 -->
</where>
</select>
十一.缓存
https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache
1.概念
缓存是存在内存中的临时数据。将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而减少与数据库的交互,减少系统开销,提高系统效率,解决了高并发系统的性能问题。
什么时候使用缓存? 在经常查询且不经常改变的数据。
mybatis默认定义了两级缓存:一级和二级缓存
2.一级缓存
- MyBatis默认情况下,只启用了本地的会话缓存(即一级缓存)
- Sqlsession级别的缓存,它仅仅对一个会话中的数据进行缓存(从获得该Sqlsession对象到释放的整个过程,缓存是有效的)
- 与数据库同一次会话(与数据库建立的连接)期间查询到的数据会放在本地缓存中。
测试: (验证Sqlsession对象在没有释放时,第二次获取是从一级缓存中获取的数据)
@Test
public void getUsers(){
SqlSession sqlSession = MybatisUtils.getSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);//就像以前的:Dao user = new DaoImpl()一样
System.out.println("--------------------第一次获取数据-------------------------");
List<User> userList1 = mapper.getUsers();
for (User user : userList1) {
System.out.println(user);
}
//sqlSession.clearCache(); //可以手动清除一级缓存
System.out.println("--------------------第二次获取数据-------------------------");
List<User> userList2 = mapper.getUsers();
for (User user : userList2) {
System.out.println(user);
}
sqlSession.close();//释放
}
结果:(第一次从数据库获取数据,需要连接到数据库;第二次获取该数据,则直接从缓存中获取了,并没有连接数据库进行获取)
3.二级缓存
- 二级缓存又称全局缓存,作用域更高
- namespace级别的缓存,整个mapper接口的所有方法中都可以拿到二级缓存数据;
- 我们与数据库建立会话,查询到的数据就会先放在一级缓存中,当这次连接关闭(Sqlsession释放)或提交,数据就从一级缓存放入二级缓存(一个Mapper接口就对应一个二级缓存,放在自己对应的Mapper缓存里)
- 新的会话就可以直接从二级缓存中取数据
二级缓存默认是关闭的,要开启二级缓存,需要:
1.在mapper.xml文件加上以下代码
<mapper namespace="com.liqingfeng.mapper.UserMapper">
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
。。。。。。
</mapper>
2.在mybatis.xml核心配置文件中开启二级缓存
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="cacheEnabled" value="true"/>
</settings>
测试:(第一次获取数据后,关闭sqlSession,然后在获取一个sqlSession,再进行第二次获取该数据)
@Test
public void getUsers(){
SqlSession sqlSession1 = MybatisUtils.getSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);//就像以前的:Dao user = new DaoImpl()一样
System.out.println("--------------------第一次获取数据-------------------------");
List<User> userList1 = mapper1.getUsers();
for (User user : userList1) {
System.out.println(user);
}
sqlSession1.close();//切记关闭资源
SqlSession sqlSession2 = MybatisUtils.getSession();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);//就像以前的:Dao user = new DaoImpl()一样
System.out.println("--------------------第二次获取数据-------------------------");
List<User> userList2 = mapper2.getUsers();
for (User user : userList2) {
System.out.println(user);
}
sqlSession2.close();//切记关闭资源
}
结果:(第二次获取数据是在mapper接口层面的范围)