JAVA进阶之Mybatis

MyBatis

概述

框架:封装通用功能,软件开发中的半成品,简化开发过程
轻量级的,持久层框架,负责完成java和数据库的通信。

代码分布:DAO+Service

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

ORM

概念:Object Relational Mapping,对象关系映射。

目标:在【java对象】 和 【关系表】 建立映射

:简化两者之间的通信过程。可以直接通信。

1. 搭建流程

1 导入依赖

// pom.xml
<!-- mybatis 依赖 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.5</version>
</dependency>
<!-- mysql驱动 依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.25</version>
</dependency>

Mapper映射文件写在resources目录下不需要下面配置

//pom.xml,使得src/main/java下的xml文件可以进入编译范围
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>

定义O和R

create table t_user(
    id int primary key auto_increment,
    name varchar(20),
    gender char(1),-- tinyint(1)
    create_time datetime
)default charset=utf8 engine=innodb;
class User{
    private Integer id;
    private String name;
    private Boolean gender;
    private Date createTime;
    //get/set....
}

M-定义映射文件

DAO接口定义不变,映射文件即Mapper文件替换之前的DAO实现

public interface UserDAO {
    public List<User> queryAll();
    public User queryOne(Integer id);
    public List<User> queryManyByName(String name);
    public List<User> queryManyByDate(Date craeteTime);
     // 明确为参数定义别名,用于mapper文件中获取
    public List<User> queryUserByIdAndName(@Param("id") Integer id, @Param("name") String name);
}
// UserDAO.xml 重点搞清楚 何为映射
<!-- 不用定义dao的实现类,此映射文件等价于dao的实现 -->
<!-- namespace="dao接口路径"-->
<?xml version="1.0" encoding="UTF-8"?>
<!-- dtd:document type definition 配置文件规范 -->
<!DOCTYPE mapper 
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhj.dao.UserDAO">
    <!--
    根据id查询用户,User queryOne(Integer id)
        select:配置查询语句
            id:可以通过id找到执行的statement,对应dao中的一个方法名
 parameterType: 参数类型 【int long float double string boolean date 自建类型(com.zhj.domain.User)】
    resultType:结果类型,查询的结果会被封装到对应类型的对象中
           #{}:相当于占位符
          #{id}:就是把 “queryOne” 方法的名为id的参数填充到占位上
		
     -->
    <select id="queryOne" parameterType="int" resultType="com.zhj.domain.User">
        select id as id,name as name,gender as gender,create_time as createTime
        from t_user
        where id=#{id}
    </select>

    <!-- 注意:返回类型实际是List<User> , 但此处只需定义User即可 -->
    <select id="queryAll" resultType="com.zhj.domain.User">
        select id,name,gender,create_time as createTime
        from t_user
    </select>

    <select id="queryManyByName" parameterType="string" resultType="com.zhj.domain.User">
        select id,name,gender,create_time as createTime
        from t_user
        where name like #{name}
    </select>
    <select id="queryManyByDate" parameterType="date" resultType="com.zhj.domain.User">
        select id,name,gender,create_time as createTime
        from t_user
        where create_time=#{createTime}
    </select>
    
    <!-- 注意:此时方法有多个参数,【#{xx}】取值时,需要@param支持 -->
    <select id="queryUsers" resultType="com.qianfeng.pojo.User" 
            parameterType="com.qianfeng.pojo.User">
        select id,name,gender,birth from t_user where id=#{id} or name like #{name}
    </select>
</mapper>

搭建配置文件

<?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>
    <!-- 使用 id为“development”的环境信息 -->
    <environments default="development">
        <environment id="development">
            <!-- 配置JDBC事务控制,由mybatis进行管理 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置数据源,采用mybatis连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <!-- 【&】是特殊字符,【&amp;】是【&】的转义 -->
                <property name="url" value="jdbc:mysql://localhost:3306/db9?useUnicode=true&amp;characterEncoding=utf8" />
                <property name="username" value="root" />
                <property name="password" value="111111" />
            </dataSource>
        </environment>
    </environments>
    <!-- 加载映射文件 -->
    <mappers>
        <!-- 使用资源的路径
        <mapper resource="com/zhj/dao/UserDAO.xml"/> -->
        <!-- 加载某个包下的映射文件 (推荐)
            要求:Mapper接口的名称与映射文件名称一致 且 必须在同一个目录下 -->
        <package name="com.zhj.dao"/>
    </mappers>
</configuration>

测试使用

  • SqlSessionFactoryBuilder:该对象负责加载MyBatis配置文件 并 构建SqlSessionFactory实例
  • SqlSessionFactory:每一个MyBatis的应用程序都以一个SqlSessionFactory对象为核心。负责创建SqlSession对象实例。
  • SqlSession:等价于jdbc中的Connection,用于完成数据操作。
//1、读取配置文件
String resource = "configuration.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//2、根据配置文件创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3、SqlSessionFactory创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();

数据操作

  • 通过SqlSession获得DAO实现(Mapper)
  • 通过DAO实现完成具体的数据操作
// 获得UserDAO的实现
// UserDAO userMapper  = sqlSession.getMapper(UserDAO.class);
UserDAO userDAO  = sqlSession.getMapper(UserDAO.class);
//根据id查询
userDAO.queryOne(1);
//查询所有
userDAO.queryAll();
//根据姓名模糊查询
userDAO.queryManyByName("%zhj%");
//根据日期查询
Date date = new GregorianCalendar(2019, 11, 12).getTime();
userDAO.queryManyByDate(date);

sqlSession.close();//关闭sqlSession

别名

在mapper文件中,`parameterType` 和 `resultType`中使用类型时:

`<select  id="xxx" parameterType="com.zhj.domain.Page" resultType="com.zhj.domain.User">`

除mybatis自动映射的类型外,其他类型都要定义完整路径,相对繁琐。可用如下两种方式简化:


// configuration.xml
<configuration>
    ...
    <typeAliases>
        <!-- 1. 单独为每个类定义别名,则 "Page"等价于"com.zhj.domain.Page"
		<typeAlias type="com.zhj.domain.Page" alias="Page"/>
        <typeAlias type="com.zhj.domain.User" alias="User"/>-->
        <!-- 2. 定义缺省包,当mapper中的类型没有定义包时,使用此配置作为默认包;
 			 则 “Page” 会自动认为是 “com.zhj.domain”中的“Page”,即“com.zhj.domain.Page”
			 则 “User” 会自动认为是 “com.zhj.domain”中的“User”,即“com.zhj.domain.User”-->
        <package name="com.zhj.domain"/>
    </typeAliases>
    ...
</configuration>

有如上别名配置后,mapper中:

<select id="xxx" parameterType="Page" resultType="User">

参数分离

在mybatis的配置文件中有一项重要且可能需要经常改动的配置,即,数据库连接参数。

可以单独进行管理,方便后续维护。

<!-- 如下四项参数 -->
<dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/db9?useUnicode=true&amp;characterEncoding=utf8"/>
    <property name="username" value="root" />
    <property name="password" value="111111" />
</dataSource>

单独定义参数文件

# 在resources目录下,创建 jdbc.properties文件
# 参数名=参数值
jdbc.user=root
jdbc.password=111111
jdbc.url=jdbc:mysql://localhost:3306/db9?useUnicode=true&characterEncoding=utf8
jdbc.driver=com.mysql.jdbc.Driver

加载参数文件

// 在 configuration.xml中
<configuration>
    <!-- 加载参数文件 -->
    <properties resource="jdbc.properties"></properties>
    ....
    ....
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <!-- 使用 ${参数名} 获取参数文件中的值。如此这四项参数需要修改时,需要改动参数文件即可 -->
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.user}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource>
        </environment>
    </environments>

# 和 $ 区分

//如果用 , 则 必 须 用 @ P a r a m 注 解 , 否 则 ,则必须用 @Param注解,否则 ,@Param{name}会认为是要从参数中取名为name的属性
public List test3(@Param(“name”) String a);

<!-- 注意${} 就是在做字符拼接,所以此处用了【‘${name}’】而不是【${name}】 
     类比jdbc的sql语句的拼接:
        String name="zhj";  
        String sql = "select ... from tt2 where name='"+name+"'";//此处是要加单引号的
     注意:sql拼接时,有sql注入的风险
-->
<select id="test3" parameterType="string" resultType="com.zhj.domain.User">
    select id,name,gender,create_time as createTime
    from tt2
    where name = '${name}'
</select>
<!-- 注意#{} 就是在做占位符,所以此处用了【#{name}】而不是【’#{name}‘】 
     类比jdbc的sql语句的拼接:
        String name="zhj";  
        String sql = "select ... from tt2 where name=?“;//此处是不加单引号的
		...
		prepareStatement.executeQuery(sql,name);//占位符赋值
-->
<select id="test3" parameterType="string" resultType="com.zhj.domain.User">
    select id,name,gender,create_time as createTime
    from tt2
    where name = #{name}
</select>

动态sql

在映射文件中,定义了要执行的sql语句,mybatis支持在sql语句中填充一些逻辑,是的sql语句可以呈现不同的语义,

即动态sql

IF

在sql中 注入 if ,可以让sql更加灵活,让一个查询语句,可以应对更多查询情景。

重点:== != > < >= <= and or

: 比较字符串需要加引号,比较数字、布尔、null不用加引号

常用:对参数是否为空的判断,动态决定sql语句的组成

情景:对用户可以有通过name 或 gender的搜索,如果没有if动态逻辑,则是要定义多个<select>

<select id="queryUsers" parameterType="User" resultType="User">
    SELECT id,name,gender,regist_time
    FROM t_user
    WHERE
    <if test="name != null and name!=''">
        name=#{name}
    </if>
    <if test="gender != null">
        AND gender=#{gender}
    </if>
</select>
<!-- 如上如果gender为null,name不为null,则sql为:
     SELECT id,name,gender,regist_time
    FROM t_user
    WHERE name=#{name}
-->
<!-- 如上如果gender不为null,name不为null,则sql为:
     SELECT id,name,gender,regist_time
    FROM t_user
    WHERE name=#{name} AND gender=#{gender}
-->

Choose

如果在多个判断中,只会有一个是可以成立的,可以使用Choose标签

<!-- 如果id不为空就按id查询。如果id为空但name不为空就按name查询。如果都为空,就查询所有男性用户。 -->
<select id="queryUsers" parameterType="User" resultType="User">
    SELECT id,name,gender,regist_time
    FROM t_user
    WHERE
    <choose> <!-- 从上到下,有任何一个when标签,判断成功则停止判断 -->
        <when test="id != null">  <!-- 判断 -->
            id > #{id}
        </when>
        <when test="name !=null"> <!-- 判断 -->
            name = #{name}
        </when>
        <otherwise>  <!-- 以上判断都不成立时,执行 -->
            gender = '1'
        </otherwise>
    </choose>
</select>

Where

<select id="queryUsers" parameterType="User" resultType="User">
    SELECT id,name,gender,regist_time FROM t_user
    WHERE
    <if test="name != null">
        name=#{name}
    </if>
    <if test="id>=1">
        AND id>#{id}
    </if>
</select>
<!-- 如果 name=null,id=3,则sql变为:
     SELECT id,name,gender,regist_time FROM t_user
     WHERE AND id>#{id}  
-->
<!-- 如果 name=null,id=0,则sql变为:
     SELECT id,name,gender,regist_time FROM t_user
     WHERE 
-->

<where> 标签中如果没有成立的条件,则不会拼接where语句

<where> 标签中如果以 and 或 or开头,会去将其去除。

<select id="queryUsers" parameterType="User" resultType="User">
    SELECT id,name,gender,regist_time
    FROM t_user
    <where>
        <if test="name == null">
            name=#{name}
        </if>
        <if test="id>=1">
            AND id>#{id}
        </if>
    </where>
</select>
<!-- 如果 name=null,id=3,则sql变为:
     SELECT id,name,gender,regist_time FROM t_user
     WHERE id>#{id}  
-->
<!-- 如果 name=null,id=0,则sql变为:
     SELECT id,name,gender,regist_time FROM t_user 
-->

Set

<set>标签主要用于 update

<!-- 只更新非空的字段 -->
<update id="updateUser" parameterType="User">
    UPDATE t_user2
    SET
    <if test="name != null">
        name = #{name},
    </if>
    <if test="gender != null">
        gender = #{gender},
    </if>
    <if test="registTime != null">
        regist_time = #{registTime}
    </if>
    WHERE id = #{id}
</update>
<!-- 如果registTime=null,name或gender!=null,则sql为:
	 UPDATE t_user2
     SET name = #{name},gender = #{gender},
     WHERE id = #{id}
	 注意:多了逗号,sql语法错误
-->

使用 <set>:

<update id="updateUser" parameterType="User">
    UPDATE t_user2
    <set>
        <if test="name != null">
            name = #{name},
        </if>
        <if test="gender != null">
            gender = #{gender},
        </if>
        <if test="registTime != null">
            regist_time = #{registTime}
        </if>
    </set>
    WHERE id = #{id}
</update>
<!-- 如果registTime=null,name或gender!=null,则sql为:
	 UPDATE t_user2
     SET name = #{name},gender = #{gender}
     WHERE id = #{id}
	 注意:<set>会自动补充一个 ”set语句“,并将最后一个逗号去除
-->

Foreach

批量查询:id为 1,3,5的数据

//DAO声明为: public List<User> queryUsers2(List<Integer> ids);
<select id="queryUsers2" resultType="User">
    SELECT id,name,gender,regist_time
    FROM t_user2
    WHERE id IN 
    <foreach collection="list" open="(" separator="," close=")" item="id" index="ind">
        #{id}
    </foreach>
</select>
<!--
	<foreach collection="list" open="(" separator="," close=")" item="id" index="ind">
    collection="list" 代表参数是List,如果是数组,要用array
    open="("   以“(” 开头
	close=")"  以“)” 结尾
    separator=","   值之间用“,”分隔
    item="id"   每次遍历的值的临时变量
    #{id}   获取每次遍历的值
    如上:标签的效果是 (值1,值2,值3)
    示例:如果传入 List{1 3 5},则最终的sql:
    【SELECT id,name,gender,regist_time
      FROM t_user2
      WHERE id IN (1,3,5)】
-->

Sql

复用sql语句

<!-- 定义一段sql -->
<sql id="order">
    select id,note,price,create_time as createTime
    from t_order
</sql>
<!-- 引用sql -->
<select id="queryOrder" parameterType="int" resultType="Order">
    <include refid="order"/>
    where id = #{id}
</select>

缓存

缓存:将数据库的数据临时的存储起来,以更好的支持查询。

问题:如果有数据,查询频繁且更新极少,此种数据如果依然每次到数据库查询,效率偏低。

解决:将如上数据,临时的存储到内存中,提供对外界的查询服务,进而减少和数据库的通信,提高查询效率。

原理:当查询数据时,查询结果会被缓存在某个内存区域中,核心存储结构={sql:查询结果};

每次发起查询时,会先找到缓存,从中尝试获取数据,如果没有找到数据,再去查数据库,并将在数**

库中查到的结果存入缓存,以供后续查询使用。

一级缓存

存储位置:SqlSession;即一个SqlSession对象发起查询后,查询结果会缓存在自己内部

有效范围:同一个SqlSession的多次查询;即,同一个SqlSession的多次相同sql的查询可以使用一级缓存

开启:不用任何配置,默认启动。

清除:sqlSession.clearCache();

二级缓存

**存储位置:SqlSessionFactory;同一个SqlSessionFactory创建的所有SqlSession发起的查询,查询结果都会缓存在 **

SqlSessionFactory内部。

有效范围:同一个SqlSessionFactory

开启:默认开启,但需要制定哪个DAO的Mapper需要使用二级缓存,定义一个 <cache>即可

注意:二级缓存必须在sqlSession.commit()sqlSession.close() 之后才生效

清除:sqlSession.rollback();//则查询的结果不会进入二级缓存

应用

<mapper namespace="com.zhj.dao.UserDAO">
    <!-- 当前mapper中的所有查询,都进入二级缓存 
         缓存数据中涉及的pojo一定要实现  Serialiable。
    -->
    <cache></cache>
    <select>...</select>
    .....
</mapper>
UserDAO userDAO1 = sqlSession1.getMapper(UserDAO.class);
UserDAO userDAO2 = sqlSession2.getMapper(UserDAO.class);
userDAO1.queryOne(1);
userDAO2.queryOne(1);
// 在开启了二级缓存的情况下,如上代码依然会查询两次数据库。
// userDAO1.queryOne(1);之后缓存只在sqlSession1中,并未进入二级缓存。userDAO2.queryOne(1);无法使用
UserDAO userDAO1 = sqlSession1.getMapper(UserDAO.class);
UserDAO userDAO2 = sqlSession2.getMapper(UserDAO.class);
userDAO1.queryOne(1);
sqlSession1.commit();//close()也可以,因为close内部流程和commit内部流程有对缓存的相同处理
userDAO2.queryOne(1);
// 此时如上代码会只查询一次数据库。
// sqlSession1.commit();执行时,会将查到的数据序列化,存入二级缓存中。userDAO2.queryOne(1)可以使用

PageHelper

使用过程

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>RELEASE</version>
</dependency>
<!-- 
    plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
    properties?, settings?, 
    typeAliases?, typeHandlers?, 
    objectFactory?,objectWrapperFactory?, 
    plugins?, 
    environments?, databaseIdProvider?, mappers?
-->
<plugins>
    <!-- com.github.pagehelper为PageHelper类所在包名 -->
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- 页号自动回归到合理数值 -->
        <property name="reasonable" value="true"/>
    </plugin>
</plugins>



<!-- spring等价配置 
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="plugins">
    <array>
      <bean class="com.github.pagehelper.PageInterceptor"></bean>
    </array>
  </property>
</bean>
-->
//使用:
PageHelper.startPage(2,3);// 第2页,每页3条数据,pageNum,pageSize
PageHelper.orderBy("id desc");//可以选择设置排序(可选)
List<User> users = mapper.queryAllUsers();//PageHelper后的第一个查询语句,会被PageHelp增强处理(可观测mysql日志)
for (User user : users) {// users中已经是分页数据
    System.out.println(user);
}	
//包装一个PageInfo,其中会持有所有分页会用到的信息:当前页号,每页多少条,共多少页,是否为第一页/最后一页,是否有下一页等。
PageInfo<User> pageInfo=new PageInfo<User>(users);

在这里插入图片描述

重要提示

PageHelper.startPage方法重要提示

只有紧跟在PageHelper.startPage方法后的第一个Mybatis的**查询(Select)**方法会被分页。

请不要配置多个分页插件

请不要在系统中配置多个分页插件(使用Spring时,mybatis-config.xmlSpring<bean>配置方式,请选择其中一种,不要同时配置多个分页插件)!

分页插件不支持带有for update语句的分页

对于带有for update的sql,会抛出运行时异常,对于这样的sql建议手动分页,毕竟这样的sql需要重视。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值