(五)MyBatis学习总结(超级详细哦,尤其适合初学者)

什么是MyBatis?

  • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
  • MyBatis 本是apache的一个开源项目ibatis, 2010年这个项目由apache 迁移到了google code,并且改名为MyBatis 。
  • 附MyBatis官方文档 : http://www.mybatis.org/mybatis-3/zh/index.html

MyBatis入门程序

  • MyBatis相关Jar包:(使用Maven构建)`
         <groupId>org.mybatis</groupId>
         <artifactId>mybatis</artifactId>
         <version>3.5.2</version>
     </dependency>`
    
  • MySQL数据库驱动包:`
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>8.0.15</version>
     </dependency>`
    
  • Junit单元测试包:`
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.12</version>
     </dependency>`
    
  • XML核心配置文件(mybatis-config.xml)构建SqlSessionFactory:在这里插入图片描述
<configuration>
<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybaits?serverTimezone=UTC&amp;allowPublicKeyRetrieval=true&amp;useSSL=false&amp;characterEncoding=UTF-8"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </dataSource>
    </environment>
</environments>
</configuration>
  • 创建一张user表:user表

环境搭建:

  • 提供实体类对象,(使用的是set方法注入)
public class User {
    private int id;
    private String last_name;
    private String email;

    public User() {
    }

    public User(int id, String last_name, String email) {
        this.id = id;
        this.last_name = last_name;
        this.email = email;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getLast_name() {
        return last_name;
    }

    public void setLast_name(String last_name) {
        this.last_name = last_name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", last_name='" + last_name + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

Tips:

平时为了避免不必要的麻烦,可以在设置实体类属性的时候,尽量保证其跟数据库字段名一致,当数据库字段名有下划线时候,实体类属性名可以采用驼峰命名规则。但是具体还是看要求,约定大于配置!!!后面也会有相关属性名跟字段名不一致的解决方案。
还有就是,最好自动写上get,set方法以及构造器和tostring方法。

  • 创建UserMapper接口(不需要实现类):
public interface UserMapper {
    //查询所有用户
    List<User> getUserList();
    }
  • 配置xml映射:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace==绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.xxx.dao.UserMapper">
    <!--select查询所有用户语句-->
    <select id="getUserList" resultType="com.xxx.pojo.User">
        /*执行sql*/
        select * from user
    </select>
</mapper>
  • 单元测试:
  • 每个基于MyBatis 的应用都是以一个 SqlSessionFactory的实例为核心的,SqlSessionFactory 的实例可以通过SqlSessionFactoryBuilder 获得。
  • SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的Configuration 实例来构建出SqlSessionFactory 实例。
编写一个MyBatis工具类获取sqlSession对象
public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    private static InputStream inputStream;

    static {
        try {
            //1.使用MyBatis获取SqlSessionFactory 实例
            String resource = "mybatis-config.xml";
            inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

  • 测试:
 @Test
    public void getUserList() {

        //1:获取SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //方式1:getMapper,执行sql
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userMapper.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        //3:关闭sqlSession
        sqlSession.close();
    }

以上是使用xml文件从SqlSessionFactory 中获取 SqlSession,使用和指定语句的参数和返回值相匹配的接口(比如 BlogMapper.class),现在你的代码不仅更清晰,更加类型安全,还不用担心可能出错的字符串字面值以及强制类型转换。
还有一种方式是:可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:(不推荐使用

try (SqlSession session = sqlSessionFactory.openSession()) {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}
  • 测试结果:

在这里插入图片描述

注意点和遇到的问题

  • MyBatis核心配置文件中,一定要配置mapper映射器:
 <!--每一个mapper.xml都需要在Mybatis核心配置文件中配置-->
    <mappers>
        <mapper resource="com/xxx/dao/UserMapper.xml"/>
    </mappers>
  • 单元测试中文乱码问题:可以在maven的pom文件中的标签内加入如下代码:
        <plugins>
            <plugin>
             <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.12.4</version>
                <configuration>
                    <argLine>
                        -Dfile.encoding=UTF-8
                    </argLine>
                </configuration>
            </plugin>
        </plugins>
  • 添加后还是有如下问题:在这里插入图片描述这个问题我上网找了很多资料,都是说这是编码为UTF-8形式即可,但是还是一直报错,最后我自己捣鼓试了一下将UTF-8修改为UTF8,居然可以了,真是夺笋啊!!!!而且,是要将项目设计的两个UserMapper,xml和mybatis-config.xml都修改才行,如下所示:
<?xml version="1.0" encoding="UTF8" ?>
  • maven静态资源过滤问题:

相信很多人都出现过如下情况:

org.apache.ibatis.binding.BindingException: Type interface com.xxx.dao.UserMapper is not known to the MapperRegistry.

表示未绑定注册mapper,有可能是idea无法扫描到所需的xml资源,解决方法如下:在maven的pom文件中的标签内加上:

 <resources>
              <resource>
                  <directory>src/main/resources</directory>
                  <includes>
                      <include>**/*.properties</include>
                      <include>**/*.xml</include>
                  </includes>
                  <filtering>true</filtering>
              </resource>
              <resource>
                  <directory>src/main/java</directory>
                  <includes>
                      <include>**/*.properties</include>
                      <include>**/*.xml</include>
                  </includes>
                  <filtering>true</filtering>
              </resource>
          </resources>

即可扫描到src下的java和resources下的所有properties和xml资源。

MyBatis常用XML配置

  • configuration(配置)
  • properties(属性)
  • settings(设置)
  • typeAliases(类型别名)
  • objectFactory(对象工厂)
  • plugins(插件)
  • environments(环境配置)
  • environment(环境变量)
  • transactionManager(事务管理器)
  • dataSource(数据源)
  • mappers(映射器)
注意:

configuration" 里的标签顺序如下:(否则报错如下信息)

"(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?
     objectWrapperFactory?,reflectorFactory?,plugins?,environments?,
     databaseIdProvider?,mappers?)"
具体还是建议查看文档吧,这里比较齐全:

XML配置大全

MyBatis的CRUD操作示例

  • 第一步仍然是搭建入门程序的环境。
  • 接着在UserMapper中编写CRUD操作方法:
public interface UserMapper {
    //查询所有用户
    List<User> getUserList();

    //根据id查询用户
    User getUserById(int id);

    //添加一个用户
    int addUser(User user);

    //修改用户
    int updateUser(User user);

    //删除一个用户
    int deleteUser(int id);

}
  • 配置xml映射
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace==绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.xxx.dao.UserMapper">
    <!--select查询所有用户语句-->
    <select id="getUserList" resultType="com.xxx.pojo.User">
        /*执行sql*/
        select * from user
    </select>

    <!--根据id查询用户-->
    <select id="getUserById" resultType="com.xxx.pojo.User" parameterType="_int">
        select * from mybaits.user where id =#{id}
    </select>

    <!--添加一个用户-->
    <insert id="addUser"  parameterType="com.xxx.pojo.User" >
        insert into mybaits.user(id,last_name,email) values (#{id},#{last_name},#{email})
    </insert>

    <!--修改用户-->
    <update id="updateUser" parameterType="com.xxx.pojo.User">
        update mybaits.user
        set last_name =#{last_name},email=#{email}
        where id =#{id};
    </update>

    <!--删除用户-->
    <delete id="deleteUser" parameterType="com.xxx.pojo.User">/*这里写int也可以*/
        delete from mybaits.user where id =#{id}
    </delete>

</mapper>
  • Select

以上第二个select这个语句名为:getUserById,接受一个 int(或 Integer)类型的参数,并返回一个User类型的对象,其中的键是列名,值便是结果行中的对应值。其中,参数形式为:#{}

  • Select元素常用的属性:
id 	在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 	将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器
resultType 	期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap 	对外部 resultMap 的命名引用。结果映射是 
flushCache 	将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache 	将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
resultOrdered 	这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false。
resultSets 	这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。 
Update,Insert,Delete常用属性跟Select差不多,只是个别不同,用到时候再记录。
  • 单元测试:
public class UserMapperTest {
    @Test
    public void getUserList() {

        //1:获取SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //方式1:getMapper,执行sql
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userMapper.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        //3:关闭sqlSession
        sqlSession.close();
    }

    @Test
    public void getUserById() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User userById = mapper.getUserById(2);
        System.out.println(userById);
        sqlSession.close();
    }

    @Test
    public void addUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int addUser = mapper.addUser(new User(4, "赵丽颖", "com@zhao"));
        if(addUser>0){
            System.out.println("插入成功!");
        }
        //一定要提交事务,否则及时输出插入成功,数据库也不会有数据
        sqlSession.commit();

        sqlSession.close();
    }

    @Test
    public void updateUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int updateUser = mapper.updateUser(new User(3, "张艺兴", "com@zhang"));
        if(updateUser>0){
            System.out.println("修改成功!");
        }
        //一定要提交事务,否则及时输出插入成功,数据库也不会有数据
        sqlSession.commit();

        sqlSession.close();
    }

    @Test
    public void deleteUser() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int deleteUser = mapper.deleteUser(2);
        if(deleteUser>0){
            System.out.println("删除成功!");
        }else {
            System.out.println("删除失败!");
        }
        //一定要提交事务,否则即使输出插入成功,数据库也不会有数据
        sqlSession.commit();

        sqlSession.close();
    }
}

解决数据库表字段名跟属性名不一致问题:(例如:字段名为last_name,属性名为lastName,若使用resultType,查询出来lastName为NULL)

  • lastName为NULL的原因为:MyBatis的自动映射机制会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) ,
    去对应的实体类中查找相应列名的set方法设值 , 由于找不到setlastName() , 所以lastName返回null.
  • 解决方法:
    方法一:为列名指定别名 , 别名和java实体类的属性名一致 。
<select id="getUserList" resultType="com.xxx.pojo.User">
  select id , last_name as lastName , eamil from user where id = #{id}
</select>

方法二:使用结果集映射->ResultMap 【推荐】

<select id="getUserList" resultType="com.xxx.pojo.User" resultMap="userMap">
        /*执行sql*/
        select * from user
    </select>
    <resultMap id="getUserList" type="com.xxx.pojo.User">
        <result column="last_name" property="name"></result>
    </resultMap>

(注意:除了select操作,其他三种都要提交事务,否则不会报错,但是会执行失败!)

作用域(Scope)和生命周期

理解我们目前已经讨论过的不同作用域和生命周期类是至关重要的,因为错误的使用会导致非常严重的并发问题。

我们可以先画一个流程图,分析一下Mybatis的执行过程!
在这里插入图片描述

  • 作用域理解
  • SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)
  • SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期
  • 由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况
  • 因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说 SqlSessionFactory 的最佳作用域是应用作用域
  • 如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭
  • 所以 SqlSession 的最佳的作用域是请求或方法作用域

日志工厂和分页

Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:

  • Log4j 2
  • Log4j
  • Apache Commons Logging
  • SLF4J
  • JDK logging

部署日志可以在MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择其它日志实现。

<configuration>
  <settings>
    ...
    <setting name="logImpl" value="LOG4J"/>
    ...
  </settings>
</configuration>

可选的值有:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,或者是实现了 org.apache.ibatis.logging.Log 接口,且构造方法以字符串为参数的类完全限定名。

日志配置步骤:(LOG4J为例子)

  1. 使用maven导入log4j的包:
		<dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
  1. 配置文件编写(log4j.properties)放到resources下
#将等级为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/xxx.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.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
  1. 在MyBatis核心配置文件的configuration标签下设置日志实现:
	 <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <setting name="logImpl" value="LOG4J"/>
     </settings>
  1. 在程序中使用log4j进行输出:
//注意导包:org.apache.log4j.Logger
static Logger logger = Logger.getLogger(MyTest.class);
  @Test
    public void testLog4j(){
        logger.info("info:进入selectUser方法");
        logger.debug("debug:进入selectUser方法");
        logger.error("error: 进入selectUser方法");
        SqlSession session = MybatisUtils.getSqlSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> users = mapper.getUserList();
        for (User user: users){
            System.out.println(user);
        }
        session.close();
    }
  • 分页的接口: (起始位置 = (当前页面 - 1 ) * 页面大小)
public interface UserMapper {
    //分页
    List<User> getLimit(Map<String,Integer> map);

}
  • 接口的关系映射配置:
    <!--分页-->
    <select id="getLimit" parameterType="map" resultType="com.xxx.pojo.User" resultMap="userMap">/*因为之前设置过resultMap,此处只需要设置与其id值同即可*/
        select * from user limit  #{startIndex},#{pageSize}
    </select>

  • 测试:
   @Test
    public void getLimit(){
       SqlSession sqlSession = MybatisUtils.getSqlSession();
       UserMapper mapper = sqlSession.getMapper(UserMapper.class);
       HashMap<String, Integer> map = new HashMap<String,Integer>();
       map.put("startIndex",1);
       map.put("pageSize",2);
       List<User> userList = mapper.getLimit(map);
       for (User user : userList) {
           System.out.println(user);
       }
   }
  • MyBatis也有一个分页插件:PageHelper,感兴趣的可以去了解一下。
Mybatis也可以使用注解进行简化开发,但是对于一些较复杂的关系,注解无法很好的完成,加上后面学习Spring后,Spring可以很好的整合MyBatis,所以MyBatis还是建议使用XML配置文件形式来开发。对注解感兴趣的同学可以自行去看。

MyBatis 数据库关系处理

  • 多对一关系处理(多个学生对应一个老师,学生关联同一个老师)

数据库设计:

  • 创建一个student表:
    在这里插入图片描述
  • 创建一个teacher表:
    在这里插入图片描述
    创建Student和Teacher实体类并提供get,set方法和构造器以及tostring方法:

Student实体类:

public class Student {
    private int id;
    private String name;
    //多个学生需要关联一个老师
    private Teacher teacher;

    public Student() {
    }

    public Student(int id, String name, Teacher teacher) {
        this.id = id;
        this.name = name;
        this.teacher = teacher;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}

Teacher实体类:

public class Teacher {
    private int id;
    private String name;

    public Teacher() {
    }

    public Teacher(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

注意:在Student类中,要表示多个学生关联一个老师,则应该创建一个Teacher类属性 teacher.

编写StudentMapper接口查询所有学生信息及其老师信息

public interface StudentMapper {

    //查询所有学生信息以及其老师信息
   public List<Student> getStudents();
}

在resources目录下编写StudentMapper.xml配置文件

注意:这里有个巨大的坑!

一般来说,我们编写的mapper接口和mapper.xml文件最好是同名而且是同一个包下的.但是mapper.xml文件不建议跟mapper接口一起放在java下的同一个包,如果包多了的话会显得缭乱,所以,都是将mapper.xml文件放在resources目录下的,我们可以创建一个相同的包名就可以了。
但是,创建相同包名的时候不要:com.xxx.mapper这种形式,这个只在java下有用,在resources下应该为:com/xxx/mapper 这样才行,否则会报mapper注册失败,即找不到资源文件的错误。
这是血的教训,我找了很久才解决,切记切记!!!!!

StudentMapper.xml配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace==绑定一个对应的Dao/Mapper接口,以后Mapper.xml文件都放在resourse下,
                                            但是要建立一个跟Mapper接口相对应得包
    注意!!这里有一个坑,当在resources下建立包时候,不要写为:com.xxx.dao
                                                      应该为:com/xxx/dao
                                           -->
<mapper namespace="com.xxx.dao.StudentMapper">


<!--
=============================嵌套查询的方式一-===================================
 使用如下数据库的嵌套查询可以输出结果,但是teacher的name为空,则下列方法不能查出
     name为空,则应该用resultMap来
   <select id="getStudents" resultType="com.xxx.pojo.Student">
        select s.id,s.name,t.name from student s,teacher t where s.tid=t.id
    </select>
-->

<!--
    <select id="getStudents" resultMap="studentMap">
        select * from student
    </select>
    <resultMap id="studentMap" type="com.xxx.pojo.Student">
        &lt;!&ndash;resultMap里面不需要对同名的,以及常规类型的属性进行处理
            只需处理不同名的,以及其他属性,如这里的Teacher对象属性
            对象使用association,集合使用collection,其标签里面的属性javaType是对象的类型(全类名)
            select标签写要关联的子查询id&ndash;&gt;

        <association column="tid" property="teacher" javaType="com.xxx.pojo.Teacher" select="getTeacher"></association>
    </resultMap>

    <select id="getTeacher" resultType="com.xxx.pojo.Teacher">
        select * from teacher where id=#{tid}
    </select>
-->



    <!--===================================嵌套查询的方式二(按照结果嵌套查询(推荐))=====================================


    第二种是直接使用sql嵌套查询语句,但是一定要给要查询的字段名起一个别名
    接着使用resultMap映射,在里面将常规结果使用result/id完成不同名的映射
    使用association或者collection分别对对象或者集合进行映射,这时候里面不需要column属性
    只要property实体类属性名和javaType的对象的全类名即可
    再在里面将该嵌套的字段不同名的属性进行映射

    -->

    <select id="getStudents" resultMap="studentMap">
        select s.id as sid,s.name as sname,t.name as tname from student s,teacher t where s.tid=t.id
    </select>
    <resultMap id="studentMap" type="com.xxx.pojo.Student">
        <result column="sid" property="id"></result>
        <result column="sname" property="name"></result>
        <association property="teacher" javaType="com.xxx.pojo.Teacher">
            <result column="tname" property="name"></result>
        </association>
    </resultMap>

</mapper>

以上有两种方法实现:一种是按查询嵌套处理,另一种是按结果嵌套处理。按照查询进行嵌套处理就像SQL中的子查询,按照结果进行嵌套处理就像SQL中的联表查询。

** 关联(association)**元素处理“有一个”类型的关系。 比如,在我们的示例中,一个博客有一个用户。关联结果映射和其它类型的映射工作方式差不多。 你需要指定目标属性名以及属性的javaType(很多时候 MyBatis 可以自己推断出来),在必要的情况下你还可以设置 JDBC 类型,如果你想覆盖获取结果值的过程,还可以设置类型处理器。
关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:

  • 嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。

  • 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集
    首先,先让我们来看看这个元素的属性。你将会发现,和普通的结果映射相比,它只在 select 和 resultMap 属性上有所不同。

property 	映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
javaType 	一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。 

关联的嵌套 Select 查询

column 	数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。 注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
select 	用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。 具体请参考下面的例子。注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}" 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。 

关联的嵌套结果映射

resultMap 	结果映射的 ID,可以将此关联的嵌套结果集映射到一个合适的对象树中。 它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet。这样的 ResultSet 有部分数据是重复的。 为了将结果集正确地映射到嵌套的对象树中, MyBatis 允许你“串联”结果映射,以便解决嵌套结果集的问题。使用嵌套结果映射的一个例子在表格以后。

在MyBatis核心配置文件中绑定mapper

    <!--每一个mapper.xml都需要在Mybatis核心配置文件中配置-->
    <mappers>
        <mapper resource="com/xxx/dao/StudentMapper.xml"/>
        <mapper resource="com/xxx/dao/TeacherMapper.xml"/>
    </mappers>

测试:

public class StudentMapperTest {
    static Logger logger = Logger.getLogger(StudentMapperTest.class);
    @Test
    public void getStudents(){
        long start = System.currentTimeMillis();
        //1:获取SqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //方式1:getMapper,执行sql
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> students = mapper.getStudents();
        for (Student student : students) {
            System.out.println(student);
        }
        long end = System.currentTimeMillis();
        System.out.println(end-start);
        //3:关闭sqlSession
        sqlSession.close();
}
}

  • 一对多数据关系处理(一个老师有多个学生)

数据库还是使用同一个

对应实体类
Student类:

public class Student {
    private int id;
    private String name;
    //一个学生对应一个老师,则不用对象类型,直接int tid
    private int tid;

    public Student() {
    }

    public Student(int id, String name, int tid) {
        this.id = id;
        this.name = name;
        this.tid = tid;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getTid() {
        return tid;
    }

    public void setTid(int tid) {
        this.tid = tid;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", tid=" + tid +
                '}';
    }
}

Teacher类:

public class Teacher {
    private int id;
    private String name;

    //一个老师有多个学生,应该是有list集合
    private List<Student> students;

    public Teacher() {
    }

    public Teacher(int id, String name, List<Student> students) {
        this.id = id;
        this.name = name;
        this.students = students;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", students=" + students +
                '}';
    }
}

注意:在Teacher类中,要表示一个老师有多个学生,则应该创建一个带泛型的List集合属性students

编写TeacherMapper接口

public interface TeacherMapper {
    List<Teacher> getTeacher(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">
<!--namespace==绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.xxx.dao.TeacherMapper">

    <select id="getTeacher" resultMap="teacherMap" parameterType="_int">
        select s.id as sid,s.name as sname,t.name as tname,t.id as tid
        from student s,teacher t
        where s.tid=t.id
        and t.id=#{tid}
    </select>
    <resultMap id="teacherMap" type="com.xxx.pojo.Teacher">
        <result column="tid" property="id"></result>
        <result column="tname" property="name"></result>
        <collection property="students" ofType="com.xxx.pojo.Student">
            <result column="sid" property="id"></result>
            <result column="sname" property="name"></result>
            <result column="tid" property="tid"></result>
        </collection>
    </resultMap>

</mapper

一对多其实跟多对一差不多,区别在于:

  • 多对一时:学生实体类用将老师对象封装为属性,使用association进行绑定,用的是JavaType,association里面映射老师属性,JavaType是用来指定pojo中属性的类型

  • 一对多时:老师实体类将学生集合封装为属性,使用collection进行绑定,用的是ofType,collection里面映射学生属性,ofType指定的是映射到list集合属性中pojo的类型

集合元素和关联元素几乎是一样的,它们相似的程度之高,以致于没有必要再介绍集合元素的相似部分

在MyBatis核心配置文件中绑定mapper

    <!--每一个mapper.xml都需要在Mybatis核心配置文件中配置-->
    <mappers>
        <mapper resource="com/xxx/dao/StudentMapper.xml"/>
        <mapper resource="com/xxx/dao/TeacherMapper.xml"/>
    </mappers>

测试:

public class TeacherMapperTest {
    @Test
    public void getTeacher(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        List<Teacher> teacher = mapper.getTeacher(1);
        for (Teacher teacher1 : teacher) {
            System.out.println(teacher1);
        }
        sqlSession.close();
    }
}

动态SQL

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

环境搭建

  1. 提供一个user数据库表,里面有id,author,title,state属性,并且提供get和set,tostring方法和构造器。
  2. 编写对应的接口类
  3. 在接口类的.xml文件进行配置:具体如下:
  • if
<!--根据条件动态查询if-->
    <select id="queryUserIf" resultType="com.xxx.pojo.User" parameterType="map">
        select * from user
        <where>
        /*这里可以使用where标签,或者直接用where 1=1*/
            <if test="title != null">
               title = #{title}
            </if>
            <if test="author != null">
               and author = #{author}
            </if>
        </where>
    </select>

queryUserIf对应接口的一个查询方法,根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询。

如果 author 等于 null,那么查询语句为 select * from user where title=#{title},由于使用了“where”标签,它会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。则,如果title为空,那么查询语句为 select * from user where author=#{author}。

  • choose (when, otherwise)

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

<!--choose,相当于swtich,case:break,只要有满足就输出,否则执行otherwise(所以其实这里后面的and可以不要,因为只有一个满足,拼接上就知道了)-->
    <select id="queryUserChoose" parameterType="map" resultType="com.xxx.pojo.User">
        select * from user
        <where>
            <choose>
                <when test="title != null">
                    title = #{title}
                </when>
                <when test="author != null">
                   and author = #{author}
                </when>
                <otherwise>
                   and state = #{state}
                </otherwise>
            </choose>
        </where>
    </select>

以上例子,传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,则返回state值。

  • trim (where, set)
<!--Set 会在sql自动添加set,且将set语句后面多余的逗号去除-->
    <update id="updateUserSet" parameterType="map">
        update user
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="author != null">
                author = #{author}
            </if>
        </set>
        where id = #{id};
    </update>

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

  • foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)比如:需要查询user表中 id 分别为1,2,3的博客信息

<select id="queryUserForeach" parameterType="map" resultType="user">
  select * from user
   <where>
       <!--
       collection:指定输入对象中的集合属性
       item:每次遍历生成的对象
       open:开始遍历时的拼接字符串
       close:结束时拼接的字符串
       separator:遍历对象之间需要拼接的字符串
       select * from user where 1=1 and (id=1 or id=2 or id=3)
     -->
       <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
          id=#{id}
       </foreach>
   </where>
</select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况 下,该属性的值是不一样的,主要有一下3种情况:

1. 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
2. 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
3. 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了

  • 上述collection的值为ids,是传入的参数Map的key,对应的mapper测试为:
@Test
public void testQueryUserForeach(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   HashMap map = new HashMap();
   List<Integer> ids = new ArrayList<Integer>();
   ids.add(1);
   ids.add(2);
   ids.add(3);
   map.put("ids",ids);
   List<User> users = mapper.queryUserForeach(map);
   System.out.println(users);
   session.close();
}
  • 单参数array数组的类型:
 1 <select id="queryUserForeach" parameterType="java.util.ArrayList" resultType="User">
2     select * from user where id in
3     <foreach collection="array" index="index" item="item" open="(" separator="," close=")">
4          #{item}
5     </foreach>
6 </select>    
  • 上述collection为array,对应的Mapper测试为:
  @Test
  public void queryUserForeachTest() {
          SqlSession session = MybatisUtils.getSession();
  		  UserMapper mapper = session.getMapper(UserMapper.class);
          int[] ids = new int[] {1,3,6,9};
          List users = userMapper.queryUserForeach(ids);
          for (User user : users)
          System.out.println(user);    
          session.close();
 }

至此,我们已经完成了与 XML 配置及映射文件相关的讨论。其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。多在实践中使用才是熟练掌握它的技巧。

MyBatis缓存

  1. 什么是缓存(Cache)?
    缓存就是用来提高查询访问速度,就是将每次的访问记录缓存在一地方,在下次进行查询时首先访问的不是数据库,而是缓存,如果在缓存中查询到了该次查询条件的结果集就直接返回结果,不再访问数据库。

  2. 为什么使用缓存?
    减少和数据库的交互次数,减少系统开销,提高系统效率。

  3. 缓存的使用场景?
    经常查询并且不经常改变的数据

MyBatis缓存
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存

  • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)

  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

  • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

  • 一级缓存
  • 同一个 SqlSession 对象, 在参数和 SQL 完全一样的情况先, 只执行一次 SQL 语句(如果缓存没有过期)

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中。

  • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

  • 一级缓存失效的四种情况:

    1、sqlSession不同(每个sqlSession中的缓存相互独立)
    2、sqlSession相同,查询条件不同
    3、sqlSession相同,两次查询之间执行了增删改操作!(因为增删改操作可能会对当前数据产生影响)
    4、sqlSession相同,手动清除一级缓存(如在配置文件中配置:flushCache=“true”,则会刷新缓存)

  • 二级缓存(要手动开启)

具体步骤如下:

  1. 开启全局缓存 【mybatis-config.xml】
<setting name="cacheEnabled" value="true"/>
  1. 去每个mapper.xml中配置使用二级缓存
<cache/>

官方示例=====>查看官方文档
<cache
 eviction="FIFO"
 flushInterval="60000"
 size="512"
 readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据

查出的数据都会被默认先放在一级缓存中

只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中

好了,本人关于MyBatis学习的分享就到此结束,具体的缓存知识可以观看官方文档或者观看其他博主优秀文章:点这里。以后我就可以直接看我的在线小本本复习和直接拿我的资源啦,你们也可以哦!
  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我实在是想不出什么好听的昵称了啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值