MyBatis

Mybatis程序示例

搭建数据库

CREATE TABLE USER(
id INT(20) NOT NULL,
NAME VARCHAR(30) DEFAULT NULL,
pwd VARCHAR(30) DEFAULT NULL,
PRIMARY KEY(id)
)ENGINE=INNODB DEFAULT CHARSET=utf8
insert into `user`(`id`,`name`,`pwd`) values (1,'dwx','123456'),(2,'dyy','1223');

导入Myabtis和mysql-connector-java的jar包

 <dependency>
       <groupId>org.mybatis</groupId>
       <artifactId>mybatis</artifactId>
       <version>3.5.6</version>
</dependency>
<dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>8.0.22</version>
</dependency>

编写Mybatis核心配置文件

<?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.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?
useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="13476110270dwx"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/deng/Mapper/userMapper.xml"/>
</mappers>
</configuration>

编写Mybatis工具类

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static{
        try{
            //创建sqlSessionFactory对象
            String resource="mybatis-config.xml";
            InputStream is= Resources.getResourceAsStream(resource);
            sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    //获取连接
    public  static SqlSession getSession(){
        return  sqlSessionFactory.openSession();
    }
}

创建实体类

public class User {
    private int id;
    private String name;
    private String pwd;
}

创建Mapper接口

public interface UserMapper {
    List<User> selectUser();
}

编写mapper.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.deng.Mapper.UserMapper">
    <select id="selectUser" resultType="com.deng.entity.User">
        select * from user
    </select>
</mapper>

程序可能扫描不到该xml文件,配置pom.xml文件即可

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

CURD操作

select,insert,update,delete都具有如下属性:

  • id:与接口中的方法名对应
  • parameterType:传入Sql语句的参数类型
  • resultType:sql语句返回值类型

insert,update,delete都需要session.commit()手动提交事务,否则操作不会提交到数据库

小结

  • 所有的增删改操作都必须提交事务
  • 接口中的参数都写上@Param
  • 可以使用map来传递参数

Mybatis配置解析

  • mybatis-config.xml是Mybatis的核心配置文件
  • 配置内容如下

MyBatis

  • 可以不配置元素,但是需要配置的元素的顺序必须一致,不然会报错。

 

 

 environments元素

    <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/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="13476110270dwx"/>
            </dataSource>
        </environment>
    </environments>
  • 配置Mybatis的多套运行环境,必须指定一个为默认的运行环境
  • 子元素节点environment
    • 具体的一套环境,id唯一标识
    • 子元素节点:transactionManager事务管理器
    • 子元素节点:dataSource数据源
      • 使用标准的JDBC数据源接口来配置JDBC连接对象的资源
      • 必须配置数据源
      • 有三种内建的数据源类型
        •  type="[UNPOOLED|POOLED|JNDI]")
        • UNPOOLED:每次被请求时都要打开和关闭来连接
        • POOLED:池,不用频繁打开和关闭连接
        • JNDI:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以 集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
      • 数据源也有很多第三方实现,如DBCP,C3P0

mappers元素

  • 映射器:定义映射sql语句文件
  • 告诉MyBatis去哪里找映射文件

Properties优化

把一些属性配置在priperties文件中。

创建db.properties文件

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username=root
password=13476110270dwx

将文件导入properties

<properties resource="db.properties"></properties>

typeAliases优化

给java类型设置一个别名

<typeAliases>
     <typeAlias type="com.deng.entity.User" alias="User"/>
</typeAliases>

配置之后,在任何地方,都可以使用User来代替com.deng.entity.User

也可指定包名

<typeAliases>
     <package name="com.deng.entity"/>
</typeAliases>

在com.deng.entity中的每一个JavaBean,会使用首字母小写的类名来作为其别名

生命周期与作用域

Mybtis执行过程

MyBatis

 

 

 作用域分析

  • SqlSessionFactoryBuilder的作用在于创建SqlSessionFactory,创建成功后就没用了,所有其作用域应该时方法作用域
  • SqlSessionFactory的作用是创建一个SqlSession对象,SqlessionFactory的生命周期应该等同于MyBatis的生命周期
  • SqlSessionFactory作为一个单例被应用共享,其作用域为应用作用域
  • SqlSession用来执行sql,应该存活在一个业务请求中,因此其最佳作用域是请求或方法作用域

ResultMap

解决属性名和字段名不一致的问题

当sql语句查询出来的列名于实体类属性列名不一致时就无法映射,使用resultMap来指定映射关系

<mapper namespace="com.deng.Mapper.UserMapper">
    <select id="selectUser" resultMap="userMap">
        select * from user
    </select>
    <resultMap id="userMap" type="com.deng.entity.User">
        <id column="id" property="id"></id>
        <!--column是查询出的数据库列名,property是对于的实体类的属性名-->
        <result column="name" property="name"></result>
        <result column="pwd" property="password"></result>
    </resultMap>
</mapper>

分页的实现

日志工厂

MyBatis可以在控制输出日志信息

标准日志实现

<settings>
        <setting name="logImpl" value="STDOUT_LOGGING" />
</settings>

Log4j

可以控制日志输出的目的地:控制台,文本等;只需要配置即可

步骤:

导入log4j的包

 <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
</dependency>

编写配置文件

#将等级为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/log.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

设置日志实现

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

Limit分页

在SQL层面实现分页

<mapper namespace="com.deng.Mapper.UserMapper">
    <select id="selectUser" parameterType="map" resultType="com.deng.entity.User">
        select * from USER limit #{startIndex},#{pageSize}
    </select>
</mapper>

RowBound分页

在java层面实现分页

  public void test1(){
        SqlSession sqlSession= MybatisUtils.getSession();
        int currentpage=1;
        int pageSize=2;
        RowBounds rowBounds=new RowBounds((currentpage-1)*pageSize,pageSize);
        List<User> users=sqlSession.selectList("com.deng.Mapper.UserMapper.selectUser",null,rowBounds);
        for (User user:users
             ) {
            System.out.println(user);
        }
        sqlSession.close();
    }

PageHelper

使用注解开发

使用注解开发就无需mapper.xml映射文件;sql类型注解为:

  • select()
  • update()
  • insert()
  • delete()

步骤:

在接口方法上添加注解

public interface UserMapper {
    @Select("select * from user")
    List<User> selectUser();
}

在mybatis配置文件中注入

<mappers>
       <mapper class="com.deng.Mapper.UserMapper"/>
</mappers>

测试

public void test1(){
        SqlSession sqlSession= MybatisUtils.getSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        List<User> userList=userMapper.selectUser();
        for (User user:userList
             ) {
            System.out.println(user);
        }
       sqlSession.close();
}

注解增删改

public interface UserMapper {
    //查
    @Select("select * from user where id=#{id}")
    User selectById(@Param("id")int id);
    //增
    @Insert("insert into user (id,name,pwd) values(#{id},#{name},#{pwd})")
    int addUser(User user);
    //改
    @Update("update user set name=#{name},pwd=#{pwd} where id=#{id}")
    int updateUser(User user);
    //删
    @Delete("delete from user where id=#{id}")
    int deleteById(@Param("id")int id);
}

测试

  @Test
    public void test1() {
        SqlSession sqlSession = MybatisUtils.getSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user1=userMapper.selectById(1);
        System.out.println(user1);
        User user=new User(4,"zzy","hffjhsajfsdhf");
        int res1=userMapper.addUser(user);
        int res2=userMapper.updateUser(user);
        int res3=userMapper.deleteById(4);
        System.out.println(res1);
        System.out.println(res2);
        System.out.println(res3);
    }

@Param注解

@Param注解用于给参数起了一个名字,在方法接收多个参数时使用。如果参数是JavaBean则不能使用

#{}与${}的区别

  • #{}代表占位符
  • ${}代表字符串替换

多对一处理

多个学生对应一个老师

创建数据库

CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO teacher(`id`, `name`) VALUES (1, '老师');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');

查询所有学生和对应的老师

查询嵌套

类似SQL中的子查询

<mapper namespace="com.deng.Mapper.StudentMapper">
    <select id="getStudents" resultMap="StudentTeacher">
        select * from student
    </select>
    <resultMap id="StudentTeacher" type="com.deng.entity.Student">
        <association property="teacher" column="tid" javaType="com.deng.entity.Teacher" select="getTeacher"></association>
    </resultMap>
    <select id="getTeacher" resultType="com.deng.entity.Teacher">
        select * from teacher
    </select>
</mapper>

 

在Mybatis核心配置文件配置StudentMapper.xml即可测试

结果嵌套

类似SQL中的联表查询

<mapper namespace="com.deng.Mapper.StudentMapper">
   <select id="getStudents" resultMap="StudentTeacher">
       select s.id sid,s.name sname,t.name tname  from student s,teacher t where s.tid=t.id
   </select>
    <resultMap id="StudentTeacher" type="com.deng.entity.Student">
        <id property="id" column="sid"/>
        <result property="name" column="sname"></result>
        <association property="teacher" javaType="com.deng.entity.Teacher">
            <result property="name" column="tname"></result>
        </association>
    </resultMap>
</mapper>

 

一对多处理

一个老师拥有多个学生

查询嵌套

<mapper namespace="com.deng.Mapper.TeacherMapper">
    <select id="getTeacher" parameterType="Integer" resultMap="TeacherStudent">
         select * from teacher where id=#{id}
    </select>
    <resultMap id="TeacherStudent" type="com.deng.entity.Teacher">
        <collection property="students" javaType="ArrayList" ofType="com.deng.entity.Student" column="id" select="getStudentsTeacherId"></collection>
    </resultMap>
    <select id="getStudentsByTeacherId" resultType="com.deng.entity.Student">
        select * from student where tid=#{id}
    </select>
</mapper>

结果嵌套

<mapper namespace="com.deng.Mapper.TeacherMapper">
    <select id="getTeacher" parameterType="Integer" resultMap="TeacherStudent">
         select t.id tid,t.name tname,s.name sname from teacher t,student s where t.id=s.tid and t.id=#{id}
    </select>
    <resultMap id="TeacherStudent" type="com.deng.entity.Teacher">
        <id property="id" column="tid"></id>
        <result property="name" column="tname"></result>
        <collection property="students" ofType="com.deng.entity.Student">
            <result property="name" column="sname"></result>
        </collection>
    </resultMap>
</mapper>

小结

  • association用于一对一和多对一
  • collection用于一对多
  • JavaType和ofType都是用来指定对象类型的
    • JavaType用来指定实体类中属性的类型
    • ofType用来指定List集合中元素的实体类属性类型

动态SQL

If语句

<select id="queryBlogIf" parameterType="Map" resultType="com.deng.entity.Blog">
        select * from blog where
        <if test="title!=null">
            title=#{title}
        </if>
        <if test="author!=null">
            and author=#{author}
        </if>
</select>

如果title为空的话语句就变成 select * from blog where and author=#{author},这是不对的,使用where标签解决

where

<select id="queryBlogIf" parameterType="Map" resultType="com.deng.entity.Blog">
        select * from blog
        <where>
            <if test="title!=null">
                title=#{title}
            </if>
            <if test="author!=null">
                and author=#{author}
            </if>
        </where>
 </select>

如果where标签包含的标签中有返回值就会插入一个where,并且如果返回的内容是AND或者OR开头的就会将它删除

set

<update id="updateBlog" parameterType="Map">
        update blog
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="author != null">
                author = #{author}
            </if>
        </set>
        where  id=#{id}
 </update>

choose

<select id="queryBlogChoose" parameterType="Map" resultType="com.deng.entity.Blog">
        select * from blog
        <where>
           <choose>
                 <when test="title!=null">
                    title=#{title}
                </when>
                <when test="author!=null">
                    author=#{author}
                </when>
                <otherwise>
                    and views = #{views}
                </otherwise>
           </choose>
        </where>
</select>

choose when otherwise会按顺序选择执行,如果第一个条件满足则choose结束,否则判断第二个条件,如果都不满足就选择otherwise

SQL片段

重用SQL代码

    <sql id="repeatCode">
        <if test="title!=null">
            title=#{title}
        </if>
        <if test="author!=null">
            and author=#{author}
        </if>
    </sql>
    <select id="queryBlogIf" parameterType="Map" resultType="com.deng.entity.Blog">
        select * from blog
        <where>
            <include refid="repeatCode"></include>
        </where>
    </select>

foreach

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

 缓存

MyBatis缓存默认定义两级缓存:一级缓存和二级缓存

  • 默认情况,只有一级缓存开启(SqlSession级别的缓存,也称本地缓存)
  • 二级缓存需要手动开启金额配置,是基于namespace级别的缓存
  • Mybatis定义了Cache接口,可以通过实现Cache接口自定义二级缓存

一级缓存

  • 也叫本地缓存:
    • 与数据库同一次会话期间查询到的数据会放在本地缓存中
    • 之后如果需要获取相同的数据,直接从缓存中取,而不用查询数据库
 public void test1() {
        SqlSession sqlSession = MybatisUtils.getSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        //初始查询,往数据库查询数据
        User user1=userMapper.selectById(1);
        //再次查询会从缓存中拿数据,不会查询数据库
        User user2=userMapper.selectById(1);
        //是同一个对象
        System.out.println(user1==user2);
        sqlSession.close();
}

分析日志可以看出结果

一级缓存失效的4种情况

  • 一级缓存是sqlSession级别的缓存,是一直开启的,我们无法关闭

一级缓存失效的4种情况:

  • sqlSession不同:每个sqlSession之间的缓存是相互独立的
 public void test1() {
        //sqlSession1
        SqlSession sqlSession1 = MybatisUtils.getSession();
        //sqlSession2
        SqlSession sqlSession2=MybatisUtils.getSession();
        //获取Mapper
        UserMapper userMapper1=sqlSession1.getMapper(UserMapper.class);
        UserMapper userMapper2=sqlSession2.getMapper(UserMapper.class);
        User user1=userMapper1.selectById(1);
        User user2=userMapper2.selectById(1);
        //由于sqlSession不同,此时查询出的两个对象不是同一个对象
        System.out.println(user1==user2);
        sqlSession1.close(); 
}
  • sqlSession相同,查询条件不同
  • sqlSession相同,查询条件相同,但是两次查询之间进行了增删改操作,就会重新查询数据库
  • 手动清除了一级缓存
public void test1() {
        //sqlSession
        SqlSession sqlSession = MybatisUtils.getSession();
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        User user1=userMapper.selectById(1);
        //清除一级缓存
        sqlSession.clearCache();
        User user2=userMapper.selectById(1);
        System.out.println(user1==user2);
        sqlSession.close();
}

二级缓存

  • 二级缓存也叫全局缓存,作用域比一级缓存高
  • 基于namespace级别的缓存(对应一个Mapper(不同的sqlSession共享))

机制:

  • 一个会话查询一条数据,该数据就会放在当前会话的一级缓存中
  • 如果当前会话关闭,对应的一级缓存也就关闭了
  • 引入二级缓存,将不同的mapper查询的数据放在对应的二级缓存中,即使关闭会话,也能从二级缓存查询内容

使用:

在MyBatis核心配置文件开启全局缓存

<setting name="cacheEnabled" value="true""></setting>

在每个mapper.xml文件里配置二级缓存

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"></cache>

测试

    public void test1() {
        //sqlSession
        SqlSession sqlSession1 = MybatisUtils.getSession();
        SqlSession sqlSession2 = MybatisUtils.getSession();
        UserMapper userMapper1=sqlSession1.getMapper(UserMapper.class);
        UserMapper userMapper2=sqlSession2.getMapper(UserMapper.class);
        User user1=userMapper1.selectById(1);
        //只有会话提交或者关闭后,一级缓存中的数据才会转到二级缓存
        sqlSession1.close();
        User user2=userMapper2.selectById(1);
        System.out.println(user1==user2);
        sqlSession2.close();
    }

结论

  • 只要开启了二级缓存,对同一个Mapper查询,可以从二级缓存取数据
  • 查出的数据默认是先放在一级缓存中,当会话关闭或者提交后才会将一级缓存中的数据转放在二级缓存中

缓存原理

MyBatis

 

 

 EhCache

第三方缓存实现:java分布式缓存

使用:

导入jar包

<dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.2.1</version>
</dependency>

在mapper.xml中使用该缓存

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

可以编写encache.xml文件进行配置,否则直接使用默认配置

上一篇:MyBatis04:使用注解开发


下一篇:004Mybatis之分页和注解开发