小白学习java之mybatis第二天

第8节:参数处理

8.1 parameterType 配置参数
8.1.1 参数的使用说明
   	上一章节中已经介绍了SQL语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。同时也可以使用实体类的包装类,本章节将介绍如何使用实体类的包装类作为参数传递。
8.1.2 参数配置的注意事项
	基本类型和String可以直接写类型名称也可以使用包名.类名的方式,例如:java.lang.String。
	实体类类型,目前我们只能使用全限定类名。
	究其原因,是mybaits在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。在今天课程的最后一个章节中将讲解如何注册实体类的别名。
mybatis 的官方文档的说明请参考下面表格数据。
别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator
这些都是支持的默认别名。我们也可以从源码角度来看它们分别都是如何定义出来的。可以参考 TypeAliasRegistery.class 的源码

在这里插入图片描述

8.2 传递 pojo 包装对象
 		开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数,Pojo 类中包含 pojo。
需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。
8.2.1 编写QueryVo
package cn.offcn.entity;
public class QueryVo {
    private User user;
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
}
8.2.2 编写持久层接口UserMapper
public interface UserMapper {

    public List<User> getUserByLikeName(QueryVo queryVo);
}
8.2.3 配置接口方法对应的sql文件
<select id="getUserByLikeName" parameterType="cn.offcn.entity.QueryVo" resultType="cn.offcn.entity.User">
        select * from user where name like #{user.name}
</select>
8.2.4 测试QueryVo对象作为参数
    @Test
    public void testGetUserByLikeName() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //创建User对象
        User user=new User();
        user.setName("%李%");
        QueryVo queryVo=new QueryVo();
        queryVo.setUser(user);
        //调用deleteUser方法进行模糊查询
        List<User> userList = userMapper.getUserByLikeName(queryVo);
        userList.forEach(System.out::print);
        //关闭连接
        MyBatisUtils.close(session);
    }
8.3 map作为参数的处理方式
8.3.1 添加接口方法参数使用map集合
 public List<User> getUserByGenderAndAge(Map<String,Object> map);
8.3.2 配置接口对应的sql配置
 <!--#{}中参数必须和Map集合中的key保存一致,表示取Map集合中指定key的值。-->
<select id="getUserByGenderAndAge" parameterType="java.util.Map" resultType="cn.offcn.entity.User">
         select * from user where gender=#{sex} and age=#{age}
 </select>
8.3.3 测试map集合作为参数
    @Test
    public void testGetUserByGenderAndAge() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        Map<String,Object> map=new HashMap<String,Object>();
        map.put("sex","男");
        map.put("age",22);
        List<User> userList = userMapper.getUserByGenderAndAge(map);
        userList.forEach(System.out::print);
        //关闭连接
        MyBatisUtils.close(session);
    }
8.4 @Param方式解决多参数处理
8.4.1 @Param注解的介绍
		@Param注解用于给方法内的参数取别名,当方法中拥有多个参数时,我们无法一次性将这些参数进行传递,尤其多个参数具有不同的数据类型时无法传递,所以我们利用@Param给每个方法中的参数取一个别名,在映射文件中使用别名进行取值。
8.4.2 添加接口方法参数使用map集合
public List<User> getUserByGenderAndBirthday(@Param("gen") String gender,Param("birth") Date birthday);
8.4.3 配置接口对应的sql配置
<select id="getUserByGenderAndBirthday" resultType="cn.offcn.entity.User">
        select * from user where gender=#{gen} and age=#{birth}
</select>
8.4.4 测试注解方法
    @Test
    public void testGetUserByGenderAndBithday() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR,1997);
        calendar.set(Calendar.DAY_OF_MONTH,10);
        calendar.set(Calendar.DAY_OF_MONTH,12);
        Date birthday=calendar.getTime();
        List<User> userList = userMapper.getUserByGenderAndBirthday("女",birthday);
        userList.forEach(System.out::print);
        //关闭连接
        MyBatisUtils.close(session);
    }

第9节:查询结果封装

9.1 resultType结果类型
9.1.1 resultType属性介绍
     	resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。我们在前面的 CRUD 案例中已经对此属性进行过应用了。需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。例如:我们的实体类此时必须是全限定类名(今天最后一个章节会讲解如何配置实体类的别名)同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。
9.1.2 resultType属性的使用
[1]基本类型
编写dao接口方法getTotalRecords参数是基本类型
   /**
     * 统计所有记录数
     * @return
     */
    public int getTotalRecords();
配置接口方法对应的sql语句
  <select id="getTotalRecords" resultType="int">
         select count(*) from user
  </select>
测试查询结果
    @Test
    public void testGetTotalRecords() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //调用getTotalRecords统计记录数
        int totalRecords= userMapper.getTotalRecords();
        //打印totalRecords
        System.out.println("总记录数:"+totalRecords);
        //关闭连接
        MyBatisUtils.close(session);
    }
[2] 实体类型
编写dao接口方法getAllInfo参数是对象类型
public List<User> getUsers();
配置接口方法对应的sql语句
<select id="getUsers" resultType="cn.offcn.entity.User">
        select * from user
</select>
测试查询结果
    @Test
    public void testGetUser() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //调用getUsers查询所有记录
        List<User> userList = userMapper.getUsers();
       //遍历结果
        userList.forEach(System.out::print);
        //关闭连接
        MyBatisUtils.close(session);
    }
9.1.3 特殊情况
如果个修改了实体类User中的id属性值,比如修改成了userId,此时查询出的结果没有把表中的id值映射到userId属性中,因为属性和表中的列名不一致,内部无法用反射技术进行映射,所以为空。
public class User  implements Serializable {

     private Integer userId;
     private String userName;
     private String gender;
     private Integer age;
     private Date userBirthday;

    public User() {}

    public User(String userName, String gender, Integer age, Date userBirthday) {
        this.userName = userName;
        this.gender = gender;
        this.age = age;
        this.birthday = userBirthday;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getUserBirthday() {
        return userBirthday;
    }

    public void setUserBirthday(Date userBirthday) {
        this.userBirthday = userBirthday;
    }
}

 @Test
    public void testGetUser() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //调用getUsers查询所有记录
        List<User> userList = userMapper.getUsers();
       //遍历结果
        userList.forEach(System.out::print);
        //关闭连接
        MyBatisUtils.close(session);
    }


经过上述测试结果发现userId属性为null,没有被赋上值。
解决方案:修改映射配置,采用别名设置,让结果集中的列与实体类中的属性对应。
<select id="getUsers" resultType="cn.offcn.entity.User">
        select id userId,name userName,gender gender,age age,birthday userBirthday from user
</select>
9.2 resultMap自定义结果类型
9.2.1 resultMap标签介绍
  		resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。     
9.2.2 定义接口方法
/**
     * 根据id查询指定User对象
     * @param id
     * @return
     */
    public User getUserById(int id);
9.2.3 配置文件中定义resultMap
<select id="getUserById" parameterType="int" resultMap="UserResultMap">
           select * from user where userId=#{id}
</select>

<resultMap id="UserResultMap" type="cn.offcn.entity.User">
</resultMap>

此处我们使用resultMap而不是resultType, resultType是直接写结果类型,resultMap是映射结果集与类中属性对应关系。
resultMap标签中的id表示一个唯一标记是resultMap的名称。
type: 表示该resultMap返回的类型。
9.2.4 使用resultMap查询
<resultMap id="UserResultMap" type="cn.offcn.entity.User">
    <id column="id" property="userId"></id>
    <result column="name" property="userName"></result>
    <result column="gender" property="gender"></result>
    <result column="age" property="age"></result>
    <result column="birthday" property="userBirthday"></result>
</resultMap>

id 标签:用于指定主键字段
result 标签:用于指定非主键字段
column 属性:用于指定数据库列名
property 属性:用于指定实体类属性名称
9.2.5 测试查询结果
    @Test
    public void testGetUserById() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //查询user
        User user = userMapper.getUserById(1);
        //打印user
        System.out.println(user);
        //关闭连接
        MyBatisUtils.close(session);
    }

第10节:主配置文件解析

  在主配置SqlMapConfig.xml中,定义了很多标签,我们现在只是使用了一部分标签 ,主配置文件中可以出现的标签 用dtd文 件进行约束。下面介绍其它标签的使用含义。
10.1 dtd规范文件
<?xml version="1.0" encoding="UTF-8" ?>
<!--

       Copyright 2009-2016 the original author or authors.

       Licensed under the Apache License, Version 2.0 (the "License");
       you may not use this file except in compliance with the License.
       You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

       Unless required by applicable law or agreed to in writing, software
       distributed under the License is distributed on an "AS IS" BASIS,
       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       See the License for the specific language governing permissions and
       limitations under the License.

-->
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>

<!ELEMENT databaseIdProvider (property*)>
<!ATTLIST databaseIdProvider
type CDATA #REQUIRED
>

<!ELEMENT properties (property*)>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>

<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>

<!ELEMENT settings (setting+)>

<!ELEMENT setting EMPTY>
<!ATTLIST setting
name CDATA #REQUIRED
value CDATA #REQUIRED
>

<!ELEMENT typeAliases (typeAlias*,package*)>

<!ELEMENT typeAlias EMPTY>
<!ATTLIST typeAlias
type CDATA #REQUIRED
alias CDATA #IMPLIED
>

<!ELEMENT typeHandlers (typeHandler*,package*)>

<!ELEMENT typeHandler EMPTY>
<!ATTLIST typeHandler
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
handler CDATA #REQUIRED
>

<!ELEMENT objectFactory (property*)>
<!ATTLIST objectFactory
type CDATA #REQUIRED
>

<!ELEMENT objectWrapperFactory EMPTY>
<!ATTLIST objectWrapperFactory
type CDATA #REQUIRED
>

<!ELEMENT reflectorFactory EMPTY>
<!ATTLIST reflectorFactory
type CDATA #REQUIRED
>

<!ELEMENT plugins (plugin+)>

<!ELEMENT plugin (property*)>
<!ATTLIST plugin
interceptor CDATA #REQUIRED
>

<!ELEMENT environments (environment+)>
<!ATTLIST environments
default CDATA #REQUIRED
>

<!ELEMENT environment (transactionManager,dataSource)>
<!ATTLIST environment
id CDATA #REQUIRED
>

<!ELEMENT transactionManager (property*)>
<!ATTLIST transactionManager
type CDATA #REQUIRED
>

<!ELEMENT dataSource (property*)>
<!ATTLIST dataSource
type CDATA #REQUIRED
>

<!ELEMENT mappers (mapper*,package*)>

<!ELEMENT mapper EMPTY>
<!ATTLIST mapper
resource CDATA #IMPLIED
url CDATA #IMPLIED
class CDATA #IMPLIED
>

<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>
10.2 properties标签详解
 	 在使用 properties 标签配置时,在xml中可以引用properties属性文件中key的值,日后修改properties属性文 件中key的值时,不用修改xml文件,从而提高了效率。
10.2.1 新建属性文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///mybatis001
jdbc.username=root
jdbc.password=root
10.2.2 引用属性文件
<properties resource="dbconfig.properties"></properties>
<dataSource type="POOLED">
     <property name="driver" value="${jdbc.driver}"/>
     <property name="url" value="${jdbc.url}"/>
     <property name="username" value="${jdbc.username}"/>
     <property name="password" value="${jdbc.password}"/>
</dataSource>
10.3 typeAliases标签详解
在前面我们讲的 Mybatis 支持的默认别名,我们也可以采用自定义别名方式来开发。
10.3.1 定义别名
<typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="user" type="cn.offcn.entity.User"/>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
<package name="cn.offcn.entity"/>
<package name=" 其它包 "/>
</typeAliases>
10.3.2 使用别名
    <select id="getUsers" resultType="user">
        select * from user
    </select>

    <select id="getUserById" parameterType="int" resultMap="UserResultMap">
           select * from user where userId=#{id}
    </select>
    <resultMap id="UserResultMap" type="user">
        <id column="id" property="userId"></id>
        <result column="name" property="userName"></result>
        <result column="gender" property="gender"></result>
        <result column="age" property="age"></result>
        <result column="birthday" property="userBirthday"></result>
    </resultMap>
10.4 mappers标签详解
mappers映射器用于指定映射文件的位置。
<!--使用相对于类路径的资源-->
<mappers>
    <mapper resource="cn/offcn/dao/UserDao.xml"/>
</mappers>

<!--使用mapper接口类路径-->
如:<mapper class="cn.offcn.dao.UserDao"/>

注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。

第11节:自动返回主键值

11.1 应用场景
		对于自增主键在某些业务中保存一个对象后,需要使用到这个主键完成后续的业务逻辑,比如:要保存订单后,还要保存订单项信息,订单项相信需要用到订单主键。所以应用场合很多,下面我们来应用一下返回主键值操作。
11.2 定义接口方法
   /**
     * 保存user对象
     * @param user
     */
    public void saveUser(User user);
11.3 配置文件对应SQL
<select id="saveUser" parameterType="cn.offcn.entity.User">
         insert into user (name,gender,age,birthday) values(#{name},#{gender},#{age},#{birthday})
</select>
11.4 添加配置
第一种方式:
<insert id="saveUser" parameterType="cn.offcn.entity.User" useGeneratedKeys="true" keyProperty="id">
         insert into user (name,gender,age,birthday) values(#{name},#{gender},#{age},#{birthday})
</insert>
useGeneratedKeys: 表示开启获取自增主键值。
keyProperty: 表示从表中取到主键值后赋给User类中的哪个属性。

第二种方式:使用selectKey标签和mysql内置函数
<insert id="saveUser" parameterType="cn.offcn.entity.User">
         <selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
              select last_insert_id()
         </selectKey>
         insert into user (name,gender,age,birthday) values(#{name},#{gender},#{age},#{birthday})
</insert>
keyColumn:指定取数据库中哪一列的值(通常指主键列)。
keyProperty: 表示取出主键值后赋值User对象的哪个属性。
resultType:  表示对象的属性类型
order:表示完后sql语句之前还是之后把主键值赋给实体类对应属性。
11.5 测试
    @Test
    public void testsaveUser() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //创建User对象
        User user=new User("刘备","男",30,new Date());
        //保存User对象
        userMapper.saveUser(user);
        //打印user的id属性值
        System.out.println(user.getId());
        //提交事务
        session.commit();
        //关闭连接
        MyBatisUtils.close(session);
    }

第12节:SQL片段

12.1 配置文件中SQL冗余
    		在开发中,SQL的拼接很常见,有很多对拼接的sql具有重复性高的特点,有sql冗余,不仅不美观还导致映射文件配置臃肿,这时最好把重复的sql抽取出来,作为公用的sql片段,尤其在动态sql中应用中更加显著,提高可重用性。
12.2 定义SQL片段
<!--使用sql标签定义一个sql片段-->
<sql id="baseColumn ">
	id,name,gender,age
</sql>
12.3 使用SQL片段
<select id="getUsers" resultType="cn.offcn.entity.User">
        select <include refid= "baseColumn"></include> from user
</select>

第13节:连接池及事务详解

 本节重点分析接池的分类、数据配置、mybatis事务的处理方式。在mybatis核心配置文件中可以引入UNPOOLED、POOLED、JNDI数据源,分别介绍三种数据源使用场景及性能。并对UNPOOLED和POOLED数所源获取链接的方式进行源码分析。讲解mybatis事务处理方式默认为手动提交,还可设置为自动提交方式需要在调用openSession(true)方法传入true,默认为false,分析其原理是使用jdbc事务,底层将调用jdbc的setAutoCommit方法进行设置。
13.1连接池技术
13.1.1 介绍
我们在前面的 WEB 课程中也学习过类似的连接池技术,使用连接池可以避免使用时每次创建连接的情况,减少获取连接的时间,每次访问可以复用连接池中的连接提高效率,而在 Mybatis 中也有连接池技术,但是它采用的是自己的连接池技术。在 MybatisSqlMapConfig.xml 配置文件中,通过<dataSourcetype="pooled">来实现 Mybatis 中连接池的配置。
13.1.2 分类
Mybatis中我们将它的数据源 dataSource 分为以下几类:
UNPOOLED 不使用连接池的数据源
POOLED 使用连接池的数据源
JNDI使用JNDI 实现的数据源

MyBatis 内部分别定义了实现了 java.sql.DataSource 接口的 UnpooledDataSourcePooledDataSource 类来表示UNPOOLED、POOLED 类型的数据源,在这三种数据源中,通常使用的是POOLED数据源。

在这里插入图片描述

实现了DataSource数据源的结构:

在这里插入图片描述

13.1.3 数据源的配置
我们的数据源配置就是在 SqlMapConfig.xml 文件中,具体配置如下:
<!-- 配置数据源(连接池)信息 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
MyBatis 在初始化时,根据<dataSource>的 type 属性来创建相应类型的的数据源DataSource,即:
type=”POOLED”:MyBatis 会创建 PooledDataSource 实例
type=”UNPOOLED” : MyBatis 会创建UnpooledDataSource实例
type=”JNDI”:MyBatis 会从 JNDI 服务上查找 DataSource 实例,然后返回使用。
13.1.4 POOLED池获取连接分析
1.PooledDataSource类,类中有一个getConnection方法
public Connection getConnection(String username, String password) throws SQLException {
    return popConnection(username, password).getProxyConnection();
}
2.在该方法中调用了popConnection方法,我们展开该方法。
private PooledConnection popConnection(String username, String password) throws SQLException {
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;

    while (conn == null) {
        //同步块代码
      synchronized (state) {
          //当空闲池不为空时,也就是空闲池中有连接
        if (!state.idleConnections.isEmpty()) {
          // Pool has available connection  直接取出一个连接返回
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else {
          // Pool does not have available connection
            //如果活动连接数小于设定的大数量
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // Can create new connection
             //创建连接放到池中
            conn = new PooledConnection(dataSource.getConnection(), this);
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }
          } else {
            // Cannot create new connection
            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
              // Can claim overdue connection
              state.claimedOverdueConnectionCount++;
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
              state.accumulatedCheckoutTime += longestCheckoutTime;
              state.activeConnections.remove(oldestActiveConnection);
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                try {
                  oldestActiveConnection.getRealConnection().rollback();
                } catch (SQLException e) {
                  /*
                     Just log a message for debug and continue to execute the following
                     statement like nothing happend.
                     Wrap the bad connection with a new PooledConnection, this will help
                     to not intterupt current executing thread and give current thread a
                     chance to join the next competion for another valid/good database
                     connection. At the end of this loop, bad {@link @conn} will be set as null.
                   */
                  log.debug("Bad connection. Could not roll back");
                }  
              }
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
              conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } else {
              // Must wait
              try {
                if (!countedWait) {
                  state.hadToWaitCount++;
                  countedWait = true;
                }
                if (log.isDebugEnabled()) {
                  log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                }
                long wt = System.currentTimeMillis();
                state.wait(poolTimeToWait);
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
              } catch (InterruptedException e) {
                break;
              }
            }
          }
        }
        if (conn != null) {
          // ping to server and check the connection is valid or not
          if (conn.isValid()) {
            if (!conn.getRealConnection().getAutoCommit()) {
              conn.getRealConnection().rollback();
            }
            conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
            conn.setCheckoutTimestamp(System.currentTimeMillis());
            conn.setLastUsedTimestamp(System.currentTimeMillis());
            state.activeConnections.add(conn);
            state.requestCount++;
            state.accumulatedRequestTime += System.currentTimeMillis() - t;
          } else {
            if (log.isDebugEnabled()) {
              log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
            }
            state.badConnectionCount++;
            localBadConnectionCount++;
            conn = null;
            if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
              if (log.isDebugEnabled()) {
                log.debug("PooledDataSource: Could not get a good connection to the database.");
              }
              throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
            }
          }
        }
      }

    }    
13.1.5 UNPOOLED池获取连接分析
源码分析:
    1. 打开UnpooledDataSource类,类中有一个getConnection方法
 public Connection getConnection() throws SQLException {
      return doGetConnection(username, password);
 }
    2. 打开doGetConnection(username, password)方法
 private Connection doGetConnection(String username, String password) throws SQLException {
     Properties props = new Properties();
     if (driverProperties != null) {
       props.putAll(driverProperties);
     }
     if (username != null) {
       props.setProperty("user", username);
     }
     if (password != null) {
       props.setProperty("password", password);
     }
     return doGetConnection(props);
 }
3.打开doGetConnection(props)方法
private Connection doGetConnection(Properties properties) throws SQLException {
     initializeDrive
     r();
     Connection connection = DriverManager.getConnection(url, properties);
     configureConnection(connection);
     return connection;
}
在doGetConnection方法中可以见到Connection connection = DriverManager.getConnection(url, properties);这句代码,该句代码就是我们在jdbc中直截获取连接的方式,也就是说没有从池中取连接,是自己创建的新连接。
13.2 事务详解
13.2.1 事务的概述
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。事务分为手动提交和自动提交两种方式。

事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
13.2.2 事务的使用
Mybatis 中事务的提交方式,本质上就是调用 JDBC 的 setAutoCommit()来实现事务控制。
@Test
public void testSaveUser() throws Exception {
 //获取SqlSession对象
 SqlSession session = MyBatisUtils.getSession();
 //调用SqlSession 创建 UserDao接口的代理对象
 UserMapper userMapper = session.getMapper(UserMapper.class);
 User user = new User();
 user.setUsername("张三");
 //执行保存操作
 int result = userMapper.saveUser(user);
 //提交事务
 session.commit();
 //关闭session
 MyBatisUtils.close(session);
 System.out.println(result);
 System.out.println(user.getId());
}

控制台输出的结果:
------------------------>代补充结果

    这是我们的 Connection 的整个变化过程,通过分析我们能够发现之前的 CUD 操作过程中,我们都要手动进行事务的提交,原因是 setAutoCommit()方法,在执行时它的值被设置为 false 了,所以我们在CUD操作中,必须通过 sqlSession.commit()方法来执行提交操作。
    
13.2.4 事务自动提交的配置
    通过上面的研究和分析,现在我们一起思考,为什么 CUD 过程中必须使用 sqlSession.commit()提交事务?主要原因就是在连接池中取出的连接,都会将调用 connection.setAutoCommit(false)方法,这样我们就必须使用 sqlSession.commit()方法,相当于使用了 JDBC 中的 connection.commit()方法实现事务提交。
现在一起尝试不进行手动提交,一样实现 CUD 操作。
工具类:
public class MyBatisUtils {

    //定义静态变量sqlSessionFactory
    private static  SqlSessionFactory sqlSessionFactory;

    //创建静态块,当MyBatisUtils类被加载时,自动执行该静态块,始初数据。
    static{
        try{
            //获取mybatis主配置文件SqlMapperConfig.xml的输入流
            InputStream inputStream= Resources.getResourceAsStream("SqlMapperConfig.xml");
            //创建SqlSessionFactoryBuilder构建者对象
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            //调用build方法返回SqlSessionFactory工厂对象
            sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        }catch (Exception e){
             e.printStackTrace();
            System.out.println("初始化数据失失败"+e.getMessage());
        }
    }

    //创建getSqlSessionFactory方法返回SqlSessionFactory对象
    public static SqlSessionFactory getSqlSessionFactory(){
        return sqlSessionFactory;
    }

    //创建一个SqlSession对象并返回
    public static SqlSession getSession(){
        return sqlSessionFactory.openSession(true);
    }

    //关闭SqlSession方法
    public static void close(SqlSession session){
        if(session!=null) session.close();
    }
}
        
注意:一定要把openSession时传入true,表示自动提交事务。
@Test
public void testSaveUser() throws Exception {
 //获取SqlSession对象
 SqlSession session = MyBatisUtils.getSession();
 //调用SqlSession 创建 UserDao接口的代理对象
 UserMapper userMapper = session.getMapper(UserMapper.class);
 User user = new User();
 user.setUsername("李四");
 //执行保存操作
 int result = userMapper.saveUser(user);
 //提交事务
 session.commit();
 //关闭session
 MyBatisUtils.close(session);
 System.out.println(result);
 System.out.println(user.getId());
}

我们发现,此时事务就设置为自动提交了,同样可以实现CUD操作时记录的保存。虽然这也是一种方式,但就编程而言,设置为自动提交方式为 false再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务情况来决定提交是否进行提交。

第14节:动态SQL

提出二阶段时进行sql组拼是在java程序中进行的,对mybatis来讲程序和sql进行了分离,是在xml中进行定义的,这时如果要进行时常变化条件查询只能使用动态sql进行sql语句拼接。重点讲解where标签、if标签 、set标签、trim标签、foreach标签的场景和使用。
14.1 介绍
    		顾名思义,SQL 是动态拼接成的,根据传入的变量值进行逻辑操作,并动态拼接,方便实现多条件下的数据库操作。 在业务逻辑复杂,即简单 SQL 无法完成时,需要拼接时就要使用动态 SQL。
		动态sql主要解决根据条件判断附加条动态sql主要解决多条件变化查询,实现自动判断记录字段是否需要更新,根据条件判断附加条sql条件,实现批量添加数据、批量修改数据、批量修删除数据等,优化sql语句,提高执行效率。
14.2 构建测试环境
14.1 创建maven项目
添加依赖
    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

创建bean

public class User  implements Serializable {

     private Integer id;
     private String name;
     private String gender;
     private Integer age;
     private Date birthday;

    public User() {}

    public User(String name, String gender, Integer age, Date birthday) {
        this.name = name;
        this.gender = gender;
        this.age = age;
        this.birthday = birthday;
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                '}';
    }
}

创建接口

public interface UserMapper {
}
14.2 主配置文件和映射文件
编写框架配置文件
<?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>
    <!-- 配置 mybatis的环境 -->
    <environments default="development">
        <!-- 配置环境 -->
        <environment id="development">
            <!-- 配置事务的类型 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置连接数据库的信息:用的是数据源【连接池】-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis001"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!--  注册UserDao接品映射文件位置 -->
    <mappers>
        <mapper resource="cn/offcn/mapper/UserMapper.xml"/>
    </mappers>
</configuration>x
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="cn.offcn.mapper.UserMapper">
</mapper>
14.3 where标签
14.3.1 where标签简介
where标签用于代替sql中的where关键字,可以根据条件判断是否附加where关键字。如果where标签中有条件成立就会附加where关键字,如果没有成立的条件就不会附加where关键字. 可以去掉离他最近一个无关的and 或or关键字.where标签的书写格式为<where>添写附加条件</where>
14.3.2 where标签使用
1)编写接口方法
   /**
     * 根据User中的字段进行查询
     * @return
     */
    public List<User> findByUser(User user);
2)使用where标签
<select id="findByUser" parameterType="cn.offcn.entity.User" resultType="cn.offcn.entity.User">
    select * from user
    <where>
        and name=#{name} and age=#{age}
    </where>
 </select>
3)测试
    @Test
    public void testFindByUser() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //创建User对象
        User user=new User();
        user.setName("张三");
        user.setAge(22);
        List<User> userList = userMapper.findByUser(user);
        //遍历
        userList.forEach(System.out::print);
        //关闭连接
        MyBatisUtils.close(session);
    }
 
14.4 if标签
14.4.1 if标签简介
	if标签表示逻辑条件判断,如果条件成立就附加<if></if>之间的sql语句,如果条件不成立就不附加<if></if>之间的sql语句。
  书写格式为:<if test="表达式">sql语句</if>
14.4.2 if标签使用
1)编写接口方法
   /**
     * 根据条件查询
     * @param user
     * @return
     */
    public List<User> findUsersByCondition(User user);
2)使用if标签
<select id="findUsersByCondition" parameterType="cn.offcn.entity.User" resultType="cn.offcn.entity.User">
        select * from user
        <where>
            <if test="name!=null">
              and  name=#{name}
            </if>
            <if test="age!=null">
               and age=#{age}
            </if>
            <if test="name!=null">
               and gender=#{gender}
            </if>
        </where>
    </select>
3)测试
    @Test
    public void testFindUsersByCondition() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //创建User对象
        User user=new User();
        user.setName("王五");
        user.setAge(20);
        user.setGender("男");
        List<User> userList = userMapper.findUsersByCondition(user);
        //遍历
        userList.forEach(System.out::print);
        //关闭连接
        MyBatisUtils.close(session);
    }

 通过产生的sql语句可以看出,当if标签中test属性表达式为true时,就会附加if标签之间的条件。
 注意:<if>标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。
14.5 set标签
14.5.1 set标签简介
set标签用于更新语句中,代替set关键字,可以有效对指定字段进行更新,提升sql的执行效率。,当set标签中有条件成立时就会附加set标签,set标签会去除无关的逗号。set标签中一般嵌套if标签进行使用其格式为
<set>
   <if test="name">
      name=#{name},
   </if>
   <if test="age">
      age=#{age},
   </if>
   ......
</set>
14.5.2 set标签使用
1)编写接口方法
   /**
     * 更新user
     * @param user
     */
    public void updateUser(User user);
2)使用set标签
<update id="updateUser" parameterType="cn.offcn.entity.User">
        update user 
        <set>
            <if test="name!=null">
                name=#{name},
            </if>
            <if test="gender!=null">
                gender=#{gender},
            </if>
            <if test="age!=null">
                age=#{age},
            </if>
            <if test="birthday!=null">
                birthday=#{birthday},
            </if>
         </set>
            where id=#{id}
    </update>
3)测试
    @Test
    public void testUpdateUser() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //创建User对象
        User user=new User();
        user.setName("王五");
        user.setAge(20);
        //更新user
        userMapper.updateUser(user);
        //提交事务
        MyBatisUtils.close(session);
        //关闭连接
        MyBatisUtils.close(session);
    }

通过产生的sql语句可以看出,当set标签中有条件成立时就会附加set关键字,字段为null时该列不会被更新。set可以忽略与sql无关的逗号。
14.6 trim标签
14.6.1 trim标签简介
trim标签为万能标签,可用于set或where等。prefix表示要附加的前缀关键字,suffix表示要附加的后缀关键字,prefixOverrides表示要忽略前置字符,suffixOverrides表示要忽略后置字符。
格式:
<trim prefix="where" prefixOverrides="and">
            <if test="name!=null">
               and name=#{name}
            </if>
            <if test="age!=null">
               and age=#{age}
            </if>
            <if test="name!=null">
               and gender=#{gender}
            </if>
</trim>
14.6.2 trim标签使用
1)修改
1)应用于where
<select id="findUsersByCondition" parameterType="cn.offcn.entity.User" resultType="cn.offcn.entity.User">
        select * from user
        <trim prefix="where" prefixOverrides="and">
            <if test="name!=null">
               and name=#{name}
            </if>
            <if test="age!=null">
               and age=#{age}
            </if>
            <if test="name!=null">
               and gender=#{gender}
            </if>
        </trim>
    </select>

2)用于set标签 
 <update id="updateUser" parameterType="cn.offcn.entity.User">
        update user
        <trim prefix="set" suffixOverrides=",">
            <if test="name!=null">
                name=#{name},
            </if>
            <if test="gender!=null">
                gender=#{gender},
            </if>
            <if test="age!=null">
                age=#{age},
            </if>
            <if test="birthday!=null">
                birthday=#{birthday},
            </if>
        </trim>
            where id=#{id}
    </update>
2)测试
@Test
    public void testFindUsersByCondition() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //创建User对象
        User user=new User();
        user.setName("王五");
        user.setAge(20);
        user.setGender("男");
        List<User> userList = userMapper.findUsersByCondition(user);
        //遍历
        userList.forEach(System.out::print);
        //关闭连接
        MyBatisUtils.close(session);
    }

    @Test
    public void testUpdateUser() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //创建User对象
        User user=new User();
        user.setName("王五");
        user.setAge(20);
        //更新user
        userMapper.updateUser(user);
        //提交事务
        MyBatisUtils.close(session);
        //关闭连接
        MyBatisUtils.close(session);
    }
14.7 choose标签
14.7.1 choose标签简介
choose标签作用条件判断来拼接指定的条件,它和if不太相同,choose似类于java中的switch语句用法,直要有条件成立,其它判断将得不到执行,如果所有条件都不成立则执行otherwise标签中的内容。
格式:
<choose>
  <when test=条件1>
    执行的代码;
  </when>
   <when test=条件2>
    执行的代码;
  </when>
  ......
  <otherwise>
      执行的代码;
  </when>
  </otherwise>
</choose>
14.7.2 choose标签使用
1)编写接口方法
   /**
     * 查询符合条件的所有user对象 
     * @param user
     * @return
     */
    public List<User> getInfoByUser(User user);
2)使用choose标签
<select id="getInfoByUser" parameterType="cn.offcn.entity.User" resultType="cn.offcn.entity.User">
        select * from user
        <where>
            <choose>
                <when test="name!=null">
                  and  name=#{name}
                </when>
                <when test="age!=null">
                  and  age=#{age}
                </when>
                <when test="gender!=null">
                  and  gender=#{gender}
                </when>
                <otherwise>
                  and  birthday='1991-10-10'
                </otherwise>
            </choose>
        </where>
    </select>
3)测试
    @Test
    public void testGetInfoByUser() throws  Exception{
        //获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        //调用SqlSession 创建 UserDao接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //创建User对象
        User user=new User();
        user.setName("张三");
        user.setAge(22);
        List<User> userList = userMapper.getInfoByUser(user);
        //遍历
        userList.forEach(System.out::print);
        //关闭连接
        MyBatisUtils.close(session);
    }

14.8 foreach标签
14.8.1 foreach标签简介
	foreach标签表示循环,对sql中有重复的部分可以使用此循环来动态拼接sql语句。可以实现批量添加、批量删除、批量更新操作。
  foreach标签中有很多的属性,请参考下面的Foreach标签属性表。
Foreach标签属性表
属性名称含义
collection指定你要使用的集合类型
item集合中每个元素。
open在起始时,需要附加字符串,只附加一次。
close在结束时,需要附加字符,只附加一次。
separator在每个循环结时需要附加的字符串。
index每个循环的索引值。
14.8.2 foreach标签使用
1)编写接口方法
   /**
     * 批量添加
     * @param userList
     */
    public void addBatchUser(List<User> userList);

    /**
     * 批量更新
     * @param userList
     */
    public void updateBatchUser(List<User> userList);

   /**
     * 批量删除
     * @param ids
     */
    public void deleteBatchUser(List<Integer> ids);
2)使用forcach标签
<!--批量添加-->
    <insert id="addBatchUser">
        insert into user (name,gender,age,birthday) values
        <foreach collection="list" item="user" separator=",">
            (#{user.name},#{user.gender},#{user.age},#{user.birthday})
        </foreach>
    </insert>

    <!--批量更新-->
    <update id="updateBatchUser">
        <foreach collection="list" item="user" separator=";">
            update  user set name=#{user.name},gender=#{user.gender},age=#{user.age},birthday=#{birthday}
            where id=#{id}
        </foreach>
    </update>

    <!--批量删除-->
    <delete id="deleteBatchUser">
          delete from user where id in
          <foreach collection="list" item="userId" separator="," open="(" close=")">
                #{userId}
          </foreach>
    </delete>
3)测试
 //批量添加测试
 @Test
 public void testAddBatchUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     List<User> userList=new ArrayList<>();
     userList.add(new User("赵丽","女",22,new Date()));
     userList.add(new User("李宁","女",25,new Date()));
     userList.add(new User("王海涛","男",20,new Date()));
     userMapper.addBatchUser(userList);
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }

 //批量更新测试
 @Test
 public void testUpdateBatchUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     List<User> userList=new ArrayList<>();
     userList.add(new User(1,"赵刚","男",24,new Date()));
     userList.add(new User(2,"白雪","女",25,new Date()));
     userList.add(new User(3,"王海燕","女",20,new Date()));
     userMapper.updateBatchUser(userList);
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }

 //批量删除测试
 @Test
 public void testDeleteBatchUser() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     UserMapper userMapper = session.getMapper(UserMapper.class);
     List<Integer> ids=Arrays.asList(1,2,3);
     userMapper.deleteBatchUser(ids);
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }

注意:mysql本身不支持批量更新,如果需要批量更新时在url中附加allowMultiQueries=true
<property name="url" value="jdbc:mysql:///mybatis002?allowMultiQueries=true"/>
    
参数是list parameterType可写可不写,
参数是array数组,parameterType不能写

第15节:多表联合查询

		多表联合查询是企业中常用技术,对于mybatis多分为一对一、一对多、多对一和多对多关系。分别介绍并演示每种关系的两种方式,分别为嵌套结果查询和嵌套查询两种方式的优缺点,并进行分析。突出嵌套结果查询是多查询方式,嵌套查询是分表查询方式,嵌套查询将可以应用延时加载技术,为讲解延时加载埋下伏笔。重点讲解association标签和collection标签及其属性的含义和用法。
15.1概述
		在开发过程中单表查询不能满足项目需求分析功能,对于复杂业务来讲,关联的表有几张,甚至几十张并且表与表之间的关系相当复杂。为了能够实业复杂功能业务,就必须进行多表查询,在mybatis中提供了多表查询的结果时映射标签,可以实现表之间的一对一、一对多、多对一、多对多关系映射。
15.2 一对一查询
15.2.1 构建数据库表
#person表
CREATE TABLE person(
    p_id INT NOT NULL AUTO_INCREMENT,
    p_name VARCHAR(30),
    PRIMARY KEY(p_id)
);

#IdCard表
CREATE TABLE idcard(
    c_id INT NOT NULL AUTO_INCREMENT,
    c_cardno VARCHAR(18),
    c_uselife DATE,
    c_person_id INT NOT NULL,
    PRIMARY KEY(c_id),
    FOREIGN KEY(c_person_id) REFERENCES person(p_id),
    UNIQUE KEY(c_cardno)
    
);

INSERT INTO person(p_name) VALUES('张三'),('李四');

INSERT INTO idcard(c_cardno,c_uselife,c_person_id)
VALUES('110112199012127821','2029-10-10',1);
INSERT INTO idcard(c_cardno,c_uselife,c_person_id)
VALUES('120114199911103491','2030-12-01',2);

15.2.2 准备项目环境
 构建maven项目,添加依赖
<dependencies>
     <dependency>
         <groupId>org.mybatis</groupId>
         <artifactId>mybatis</artifactId>
         <version>3.4.6</version>
     </dependency>
     <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>5.1.47</version>
         <scope>runtime</scope>
     </dependency>
     <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.12</version>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>log4j</groupId>
         <artifactId>log4j</artifactId>
         <version>1.2.17</version>
     </dependency>
 </dependencies>
编写框架配置文件sqlMapConfig.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>
 <!-- 配置 mybatis的环境 -->
 <environments default="development">
     <!-- 配置环境 -->
     <environment id="development">
         <!-- 配置事务的类型 -->
         <transactionManager type="JDBC"></transactionManager>
         <!-- 配置连接数据库的信息:用的是数据源【连接池】-->
         <dataSource type="POOLED">
             <property name="driver" value="com.mysql.jdbc.Driver"/>
             <property name="url" value="jdbc:mysql://localhost:3306/mybatis002"/>
             <property name="username" value="root"/>
             <property name="password" value="root"/>
         </dataSource>
     </environment>
 </environments>
 <mappers>
      <package name="cn.offcn.dao"></package>
 </mappers
</configuration>
15.2.3 方式1:嵌套结果方式
1)创建数据模型
package cn.offcn.entity;

public class Person {

    private int id;
    private String 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;
    }
    
}
public class IdCard {

    private int id;
    private String cardno;
    private Date useLife;
    

    public int getId() {
        return id;
    }

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

    public String getCardno() {
        return cardno;
    }

    public void setCardno(String cardno) {
        this.cardno = cardno;
    }

    public Date getUseLife() {
        return useLife;
    }

    public void setUseLife(Date useLife) {
        this.useLife = useLife;
    }
 
}


2)编写sql语句
实现查询个人信息时,也要查询个人所对应的身份证信息。
select p.*,c.* from 
person p,
idcard c
where p.p_id=c.c_person_id and p.p_id=1;

3)编写类
public class PersonIdCard extends Person{

    private String cardno;
    private Date useLife;
    public String getCardno() {
        return cardno;
    }

    public void setCardno(String cardno) {
        this.cardno = cardno;
    }

    public Date getUseLife() {
        return useLife;
    }

    public void setUseLife(Date useLife) {
        this.useLife = useLife;
    }
}
4)定义持久层接口
public interface PersonDao{
    
    /**
    * 根据id查询person对象
    */
    public PersonIdCard getPersonById(int id);
}
5)定义映射文件
<?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="cn.offcn.dao.PersonDao">
<!-- 配置查询操作-->
<select id="getPersonById" resultMap="PersonResultMap">
    select p.*,c.* from 
    person p,
    idcard c
    where p.p_id=c.c_person_id and p.p_id=#{id};
</select>
<resultMap id="PersonResultMap" type="cn.offcn.entity.PersonIdCard">
    <id column="p_id" property="id"></id>
    <result column="p_id" property="id"></result>
    <result column="p_name" property="name"></result>
    <result column="c_cardno" property="cardno"></result>
    <result column="c_uselife" property="useLife"></result>
</resultMap>
</mapper>
6)创建测试类
@Test
 public void testGetPersonById() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     PersonDao personDao = session.getMapper(PersonDao.class);
     PersonIdCard personIdCard=personDao.getPersonById(1);
      //打印
     System.out.println(personIdCard);
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }
15.2.4 方式2:嵌套查询方式
1)修改Person类:添加idCard属性
public class Person {

    private int id;
    private String name;
    private IdCard idCard;

    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 IdCard getIdCard() {
        return idCard;
    }

    public void setIdCard(IdCard idCard) {
        this.idCard = idCard;
    }
    
}
2)修改接口中的方法
public interface PersonDao{
    
    /**
    * 根据id查询person对象
    */
    public Person getPersonById(int id);
}
3)定义持久层接口
public interface IdCardDao{
    
    /**
    * 根据c_person_id查询IdCard对象
    */
    public Person getIdCardByPersonId(int id);
}
4)定义映射文件
<?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="cn.offcn.dao.IdCard">
<!-- 配置查询操作-->
<select id="getIdCardByPersonId" resultMap="IdCardResultMap">
    select * from 
    idcard c
    where c_person_id=#{id};
</select>
<resultMap id="IdCardResultMap" type="cn.offcn.entity.IdCard">  
        <id column="c_id" property="id"></id>
        <result column="c_cardno" property="cardno"></result>
        <result column="c_uselife" property="useLife"></result>
</resultMap>
</mapper>
5)修改PersonDao.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="cn.offcn.dao.PersonDao">
<!-- 配置查询操作-->
<select id="getPersonById" resultMap="PersonResultMap">
    select * from 
    person p,
    where p_id=#{id};
</select>
<resultMap id="PersonResultMap" type="cn.offcn.entity.Person">
    <id column="p_id" property="id"></id>
    <result column="p_id" property="id"></result>
    <result column="p_name" property="name"></result>
    <!--映射Person类中的复杂字段idCard对象属性-->
    <association property="idCard" javaType="IdCard" column="p_id" 
                 select="cn.offcn.dao.IdCard.getIdCardByPersonId">
    </association>
</resultMap>
</mapper>

column:表示取上次查询出来的指定列的值,做为select属性所指定的查询的输入值。
select:表示指定的查询.
6)加入测试方法
@Test
 public void testGetPersonById() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     PersonDao personDao = session.getMapper(PersonDao.class);
     Person person=personDao.getPersonById(1);
     //打印
     System.out.println(person);
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }
15.3 一对多查询
15.3.1 创建数据库表
CREATE TABLE department(
    d_id INT NOT NULL AUTO_INCREMENT,
    d_name VARCHAR(100),
    PRIMARY KEY(d_id)
);

CREATE TABLE employee(
    e_id INT NOT NULL AUTO_INCREMENT,
    e_name VARCHAR(30),
    e_gender VARCHAR(6),
    e_age INT,
    e_depart_id INT,
    PRIMARY KEY(e_id),
    FOREIGN KEY(e_depart_id) REFERENCES department(d_id)
);
15.3.2 方式1:嵌套结果方式
1)创建数据模型
public class Department {

    private int id;
    private String 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;
    }

   
}

public class Employee {

    private int id;
    private String name;
    private String gender;
    private Integer age;
    public Employee(){}

    public Employee(int id,String name, String gender, int age) {
        this.id=id;
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
    public Employee(String name, String gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    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 String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

2)编写查询语句
  SELECT d.*,e.* 
  FROM 
  department d, employee e 
  WHERE
  e.e_depart_id=d.d_id AND d.d_id=#{id}
3)加入List属性
 private List<Employee> emps;
 public List<Employee> getEmps() {
        return emps;
 }

 public void setEmps(List<Employee> emps) {
        this.emps = emps;
 }
4)加入查询方法
import cn.offcn.entity.Department;

public interface DepartmentMapper {
    public Department getDepartmentById(int id);
} 
5)映射文件配置
<?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="cn.offcn.mapper.DepartmentDao">
       <select id="getDepartmentById" resultMap="DepartemntResultMap2">
           SELECT d.*,e.* FROM department d, employee e WHERE
           e.e_depart_id=d.d_id AND d.d_id=#{id}
       </select>
    <resultMap id="DepartemntResultMap" type="Department">
          <id column="d_id" property="id"></id>
          <result column="d_name" property="name"></result>
          <collection property="emps" ofType="Employee">
               <id column="e_id" property="id"></id>
               <result column="e_name" property="name"></result>
               <result column="e_gender" property="gender"></result>
               <result column="e_age" property="age"></result>
          </collection>
    </resultMap>
    collection:当属性为集合时,使用collection标签进行映射。
6)测试方法
 @Test
 public void testGetDepartentById() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     DepartmentDao departmentDao = session.getMapper(DepartmentDao.class);
     Department dept=departmentDao.getDepartentById(1);
     //打印
     System.out.println(dept);
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }
15.3.3 方式2:嵌套查询的方式
1)定义持久层接口
public interface EmployeeDao {
    public List<Employee> getEmployeeByDepartId(int id);
}
2)定义查询配置信息
 <select id="getEmployeeByDepartId" resultType="Employee">
          select e_id id,e_name name,e_gender gender,e_age age
          from employee where e_depart_id=#{id}
 </select>
3)修改配置文件
 <select id="getDepartmentById" resultMap="DepartemntResultMap">
          select * from department where d_id=#{id}
       </select>
    <resultMap id="DepartemntResultMap" type="Department">
           <id column="d_id" property="id"></id>
           <result column="d_name" property="name"></result>
           <collection property="emps" ofType="Employee" column="d_id"
                    select="cn.offcn.mapper.EmployeeMapper.getEmployeeByDepartId">
           </collection>
    </resultMap>

此处变为单表查询,使分表查询方式进行查询。
4)测试方法
@Test
 public void testGetDepartentById() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 UserDao接口的代理对象
     DepartmentDao departmentDao = session.getMapper(DepartmentDao.class);
     Department dept=departmentDao.getDepartentById(1);
     //打印
     System.out.println(dept);
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }
15.4 多对多查询
15.4.1 创建数据库表
CREATE TABLE student(
    sid INT NOT NULL AUTO_INCREMENT,
    sname VARCHAR(30),
    PRIMARY KEY(sid)
);

CREATE TABLE teacher(
    tid INT NOT NULL AUTO_INCREMENT,
    tname VARCHAR(30),
    PRIMARY KEY(tid)
);

CREATE TABLE student_teacher(
    s_id INT NOT NULL,
    t_id INT NOT NULL,
    PRIMARY KEY(s_id,t_id),
    FOREIGN KEY(s_id) REFERENCES student(sid),
    FOREIGN KEY(t_id) REFERENCES teacher(tid)
);
INSERT INTO student(sname) VALUES('张三'),('李四');
INSERT INTO teacher (tname) VALUES('刘老师'),('李老师');
INSERT INTO student_teacher(s_id,t_id) 
VALUES(1,1),(1,2),(2,1)
15.4.2 方式1:嵌套结果方式
1)创建数据模型
public class Student {

    private int id;
    private String 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;
    }
}

public class Teacher {

    private int id;
    private String 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;
    }
}

public class StudentTeacher {
    private int sid;
    private int tid;
    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public int getTid() {
        return tid;
    }

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

2)编写sql语句
SELECT s.*,ts.*,t.* 
FROM 
student s,student_teacher st,teacher t
WHERE 
s.sid=st.s_id AND st.t_id=t.tid AND s.sid=1
3)加入List属性
private List<StudentTeacher> studentTeacherList;
public List<StudentTeacher> getStudentTeacherList() {
        return studentTeacherList;
}
public void setStudentTeacherList(List<StudentTeacher> studentTeacherList) {
        this.studentTeacherList = studentTeacherList;
}
4)加入Teacher属性
private Teacher teacher;
public Student getStudent() {
        return student;
}
public void setStudent(Student student) {
        this.student = student;
}
5)加入查询方法
import cn.offcn.entity.Student;
public interface StudentDao {
    public Student getStudentById(int id);
}
6)映射文件配置
<select id="getStudentById" resultMap="StudentResultMap">
        SELECT s.*,ts.*,t.* FROM student s,teacher_student ts,teacher t
        WHERE s.sid=ts.s_id AND ts.t_id=t.tid AND s.sid=#{id}
    </select>
    <resultMap id="StudentResultMap" type="Student">
         <id column="sid" property="id"></id>
         <result column="sname" property="name"/>
         <collection property="studentTeacherList" ofType="StudentTeacher">
              <result column="s_id" property="sid"></result>
              <result column="t_id" property="tid"></result>
              <association property="teacher" javaType="Teacher">
                  <id column="tid" property="id"></id>
                  <result column="tname" property="name"></result>
              </association>
         </collection>
    </resultMap>
7)测试方法
@Test
 public void testGetStudentById() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 StudentDao接口的代理对象
     StudentDao studentDao = session.getMapper(StudentDao.class);
     Student student= studentDao.getStudentById(1);
     //打印
     System.out.println(tudent);
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }
15.4.3 方式2:嵌套查询方式
1)定义中间表的持久层接口
public interface StudentTeacherDao {
    public List<StudentTeacher> getStudentTeacherBySid(int sid);
}
2)定义查询配置信息
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.offcn.dao.StudentTeacherDao">

    <select id="getStudentTeacherBySid" resultMap="StudentTeacherResultMap">
         select * from teacher_student where s_id=#{sid}
    </select>
    <resultMap id="StudentTeacherResultMap" type="StudentTeacher">
           <result column="s_id" property="sid"></result>
           <result column="t_id" property="tid"></result>
           <association property="teacher" javaType="Teacher"
             column="t_id" select="cn.offcn.dao.TeacherMapper.getTeacherByTid">              </association>
    </resultMap>
</mapper>
3)定义持久层的接口
public interface TeacherDao {

    public Teacher getTeacherByTid(int tid);
}
4)定义映射文件配置
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.offcn.dao.TeacherDao">
    <select id="getTeacherByTid" resultType="Teacher">
            select tid id,tname name from teacher where tid=#{tid}
    </select>
</mapper>
5)修改持久层的方法
public interface StudentMapper {
    public Student getStudentById(int id);
}
6)修改查询配置信息
<select id="getStudentById" resultMap="StudentResultMap">
          select * from student where sid=#{id}
    </select>
    <resultMap id="StudentResultMap" type="Student">
        <id column="sid" property="id"></id>
        <result column="sname" property="name"/>
        <collection property="studentTeacherList" ofType="StudentTeacher"
        column="sid"
        select="cn.offcn.mapper.StudentTeacherMapper.getStudentTeacherBySid">
        </collection>
    </resultMap>
7)测试方法
@Test
 public void testGetStudentById() throws  Exception{
     //获取SqlSession对象
     SqlSession session = MyBatisUtils.getSession();
     //调用SqlSession 创建 StudentDao接口的代理对象
     StudentDao studentDao = session.getMapper(StudentDao.class);
     Student student= studentDao.getStudentById(1);
     //打印
     System.out.println(tudent);
     //提交事务
     session.commit();
     //关闭连接
     MyBatisUtils.close(session);
 }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值