13.Mybatis

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&amp;useUnicode=true&amp;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&amp;useUnicode=true&amp;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&amp;useUnicode=true&amp;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接口层面的范围)
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值