mybatis 事务 | 动态SQL | 多表查询

四,Mybatis连接池和事务深入

1.连接池

在 WEB 课程中学习过连接池技术,而在 Mybatis 中也有连接池技术,但是它采用的是自己的连接池技术。在 Mybatis 的主配置文件中,通过<dataSource type="pooled">来实现 Mybatis 中连接池的配置。

Mybatis 将它自己的数据源分为三类:

  • UNPOOLED 不使用连接池的数据源
  • POOLED 使用连接池的数据源
  • JNDI 使用 JNDI 实现的数据源

相应地,MyBatis 内部分别定义了实现了 java.sql.DataSource 接口的 UnpooledDataSource,PooledDataSource 类来表示 UNPOOLED、POOLED 类型的数据源。

一般采用的是 POOLED 数据源(很多时候我们所说的数据源就是为了更好的管理数据库连接,也就是我们所说的连接池技术)连接池其实就是一个容器,容器就可以用集合来充当,而且他必须具有,队列的特性,先进先出。还得是线程安全的,不能让多个线程拿到同一个连接。

1)Mybatis中数据源的配置

我们的数据源配置就是在 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 实例,然后返回使用

2)Mybatis中datasource的存取

MyBatis是通过工厂模式来创建数据源 DataSource 对象的, MyBatis定义了抽象的工接口:org.apache.ibatis.datasource.DataSourceFactory,通过其 getDataSource()方法返回数据源DataSource。

DataSourceFactory 源码:
public interface DataSourceFactory {
 	void setProperties(Properties props);
 	DataSource getDataSource();
}

MyBatis 创建了 DataSource 实例后,会将其放到 Configuration 对象内的 Environment 对象中, 供以后使用。(具体可以查看一个类,叫 XMLConfigBuilder)

3)Mybatis中连接的获取过程分析

当我们需要创建 SqlSession 对象并需要执行 SQL 语句时,这时候 MyBatis 才会去调用 dataSource 对象来创建java.sql.Connection对象。也就是说,java.sql.Connection对象的创建一直延迟到执行SQL语句的时候。
	@Test
	public void testSql() throws Exception {
		InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
		SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
		SqlSession sqlSession = factory.openSession();
		List<User> list = sqlSession.selectList("findUserById",41);
		System.out.println(list.size());
	}
	
只有当第 4 句 sqlSession.selectList("findUserById"),才会触发 MyBatis 在底层执行下面这个方法来创建 java.sql.Connection 对象。

查看加载过程:
通过断点调试,在 PooledDataSource 中找到如下 popConnection()方法,如下所示
    //386行左右
    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();
              ....
            //此处省略一部分,暂时看不懂
          }
          
大概意思就是:底层有两个池子,一个是空闲池,一个是存放活跃连接的池子,当获取连接的时候,首先去空闲池子看看有没有空闲的连接,有的话直接拿走,没有的话,就去活跃池子,看看到没到最大连接数,如果到了,就把最先活跃的连接拿过来,把里面绑定的数据都清了,然后拿来用。排序规则就是,拿走最先进来的,然后后面的依次向前补位,比如说拿走0,那么1就会向前补位,变成0,然后后面的依次向前补位。

2.事务控制

1)jdbc事务回顾

在 JDBC 中我们可以通过手动方式将事务的提交改为手动方式,通过 setAutoCommit()方法就可以调整。
查找jdk文档:
默认为自动提交,当做单个事务处理,可以通过设置true,false来改变。

2)mybatis事务分析

框架的本质也是调用jdk的这个方法,只是进行了一些处理。
对于之前的增删该方法:
通过查看控制台的日志,可以发现,mybatis对于增删改,默认提交方式是false,我们要在提交之后将他的提交方式设置为true,或者在session.getCommit()方法的()里面直接传一个true。

五,动态SQL语句

我们根据实体类的不同取值,使用不同的 SQL 语句来进行查询。比如在 id 如果不为空时可以根据 id 查询,
如果 username 不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。

1.sql

sql 标签是用于抽取可重用的 sql 片段,将相同的,使用频繁的 SQL 片段抽取出来,单独定义,方便多次引用。

在UserDao.xml中定义:

<!-- 抽取重复的语句代码片段 -->
<sql id="defaultSql">
  select * from user
</sql>

对抽取出来的SQL进行引用

<include refid="defaultSql"/>

2.if

Dao层:

/**
 * 根据id查询
 */
User findById(User user);

配置文件:

<!--根据id查询-->
<select id="findById" parameterType="com.atguigu.domain.User" 	 
        resultType="com.atguigu.domain.User">
  <include refid="defaultSql"/>
  	where 1=1
  <if test="id!=null and id!=''">
    and id = #{id};
  </if>
</select>

注意:<if>标签的 test 属性中写的是对象的属性名,如果是包装类的对象要使用 OGNL 表达式的写法。

另外要注意 where 1=1 的作用。

3.where

Dao层:

/**
 * 根据id查询
 */
User findById(User user);

配置文件:

 <!--根据id查询-->
<select id="findById" parameterType="com.atguigu.domain.User" 
        resultType="com.atguigu.domain.User">
  <include refid="defaultSql"/>
  <where>
    <if test="id!=null and id!=''">
      and id = #{id}
    </if>
  </where>
</select>

有了where就不用写1=1了。

4.trim

Trim 可以在条件判断完的 SQL 语句前后 添加或者去掉指定的字符。
prefix: 添加前缀
prefixOverrides: 去掉前缀
suffix: 添加后缀
suffixOverrides: 去掉后缀

<select id="findById" parameterType="com.atguigu.domain.User" 
        resultType="com.atguigu.domain.User">
  select id,last_name,email,gender from tbl_employee
  <trim prefix="where" suffixOverrides="and">
    <if test="id!=null">
   	 id = #{id} and
    </if>
    <if test="lastName!=null &amp;&amp; lastName!=&quot;&quot;">
    	last_name = #{lastName} and
    </if>
    <if test="email!=null and email.trim()!=''">
   	 email = #{email} and
    </if>
    <if test="&quot;m&quot;.equals(gender) or &quot;f&quot;.equals(gender)">
    	gender = #{gender}
    </if>
  </trim>
</select>

5.set

主要是用于解决修改操作中 SQL 语句中可能多出逗号的问题。

<update id="updateEmpByConditionSet">
	update tbl_employee
    <set>
        <if test="lastName!=null &amp;&amp; lastName!=&quot;&quot;">
       	 last_name = #{lastName},
        </if>
        <if test="email!=null and email.trim()!=''">
        	email = #{email} ,
        </if>
        <if test="&quot;m&quot;.equals(gender) or &quot;f&quot;.equals(gender)">
        	gender = #{gender}
        </if>
    </set>
    where id =#{id}
</update>

6.choose(when 、otherwise)

主要是用于分支判断,类似于 java 中的 switch case,只会满足所有分支中的一个。

<select id="findById" resultType="com.atguigu.domain.User">
  select id ,last_name, email,gender from tbl_employee
  <where>
      <choose>
          <when test="id!=null">
         	 id = #{id}
          </when>
          <when test="lastName!=null">
          	 last_name = #{lastName}
          </when>
          <when test="email!=null">
          	 email = #{email}
          </when>
          <otherwise>
          	 gender = 'm'
          </otherwise>
      </choose>
  </where>
</select>

7.foreach

foreach 主要用户循环迭代。

  • collection: 要迭代的集合,注意编写时不要写#{}
  • item: 当前从集合中迭代出的元素,生成的变量名
  • open: 开始字符
  • close:结束字符
  • separator: 元素与元素之间的分隔符
  • index:迭代的是 List 集合: index 表示的当前元素的下标;迭代的Map集合:index表示当前元素的key。

Dao层:

/**
 * parameterType传递pojo类型参数
 * 根据名字查询
 */
List<User> find2(MyQuery query);

配置文件

<!--
	parameterType传递pojo类型参数,根据名字查询
	select 字段 from user where id in (?)
-->
<select id="find2" resultType="com.atguigu.domain.User"
        parameterType="com.atguigu.domain.MyQuery">
  select * from user
  <where>
    <if test="ids!=null and ids.size()>0">
      <foreach collection="ids" item="id" open="id in(" close=")" separator=",">
        #{id}
      </foreach>
    </if>
  </where>
</select>

六,Mybatis多表查询之一对多

1.一对一查询

1)需求分析

需求:
查询所有账户信息,关联查询下单用户信息。

注意:
因为一个账户信息只能供某个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询。
如果从用户信息出发查询用户下的账户信息则为一对多查询,因为一个用户可以有多个账户。

2)编写SQL语句

SELECT 
 account.*,
 user.username,
 user.address
FROM
 account,user
WHERE account.uid = user.id

3)编写实体类

public class Account implements Serializable {
    private Integer id;
    private Integer uid;
    private double money;
    private User user;
  
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

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

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", uid=" + uid +
                ", money=" + money +
                ", user=" + user +
                '}';
    }
}

4)编写Dao层

public interface AccountDao {
    //查询所有账户,同时获取账户的所属用户信息
    List<Account> findAll();
}

5)配置文件

<mapper namespace="com.atguigu.dao.AccountDao">
    <!-- 建立对应关系 -->
    <resultMap type="account" id="accountMap">
        <id column="aid" property="id"/>
        <result column="uid" property="uid"/>
        <result column="money" property="money"/>
      
        <!-- 它是用于指定从表方的引用实体属性的 -->
        <!--
			javaType : 一个Java类的完全限定名,或一个类型别名。
			jdbcType : JDBC类型是仅仅需要对插入,更新和删除操作可能为空的列进行处理。
		-->
        <association property="user" javaType="user">
            <id column="id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
            <result column="birthday" property="birthday"/>
            <result column="address" property="address"/>
        </association>
    </resultMap>
    <select id="findAll" resultMap="accountMap">
        select u.* ,a.id as aid,a.uid,a.money
        from user u ,account a
        where a.uid=u.id;
    </select>
</mapper>

6)测试类

public class Test1 {
    private InputStream is;
    private SqlSession session;
    private AccountDao accountDao;
  
    @Before
    public void init()throws Exception {
        is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        session = factory.openSession();
        accountDao = session.getMapper(AccountDao.class);
    }
    @After
    public void destory()throws Exception{
        session.commit();//默认自动提交为false
        session.close();
        is.close();
    }
    @Test
    public void test(){
        List<Account> accounts = accountDao.findAll();
        for (Account account:accounts){
            System.out.println(account);
        }
    }
}

2.一对多查询

1)需求分析

需求:
查询所有用户信息及用户关联的账户信息。

分析:
用户信息和他的账户信息为一对多关系,并且查询过程中如果用户没有账户信息,此时也要将用户信息查询出来,左外连接查询比较合适。

2)编写sql语句

SELECT
u.*, acc.id id,
acc.uid, acc.money
FROM user u
LEFT JOIN account acc ON u.id = acc.uid

3)编写实体类

public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    private List<Account> accounts;

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                ", accounts=" + accounts +
                '}';
    }
}

4)编写Dao层

public interface UserDao {
	//查询所有用户信息及用户关联的账户信息。
    List<User> findAll();
}

5)配置文件

<mapper namespace="com.atguigu.dao.UserDao">
    <resultMap type="user" id="userMap">
        <id column="id" property="id"></id>
        <result column="username" property="username"/>
        <result column="address" property="address"/>
        <result column="sex" property="sex"/>
        <result column="birthday" property="birthday"/>
      
        <!-- collection:是用于建立一对多中集合属性的对应关系
        ofType属性: 用于指定集合元素的数据类型
        -->
        <collection property="accounts" ofType="account">
            <id column="aid" property="id"/>
            <result column="uid" property="uid"/>
            <result column="money" property="money"/>
        </collection>
    </resultMap>
  
    <select id="findAll" resultMap="userMap">
        select u.*,a.id as aid,a.uid,a.money
        from user u
                 left join account a
                           on u.id = a.uid;
    </select>
</mapper>

6)测试类

public class Test2 {
    private InputStream is;
    private SqlSession session;
    private UserDao userDao;
  
    @Before
    public void init()throws Exception {
        is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        session = factory.openSession();
        userDao = session.getMapper(UserDao.class);
    }
    @After
    public void destory()throws Exception{
        session.commit();//默认自动提交为false
        session.close();
        is.close();
    }
    @Test
    public void test(){
        List<User> users = userDao.findAll();
        for (User u:users){
            System.out.println(u);
        }
    }
}

七,多表查询之多对多

多对多实际上就是互相一对一。可以借助一张中间表,两个表的主键都存在中间表中,然后两张表分别指向中间表。

1)需求分析

需求:
实现查询所有对象并且加载它所分配的用户信息。

分析:
查询角色我们需要用到Role表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中
间表(USER_ROLE 表)才能关联到用户信息。

2)编写sql语句

SELECT
 r.*,u.id uid,
 u.username username,
 u.birthday birthday,
 u.sex sex,
 u.address address
FROM 
 ROLE r
INNER JOIN 
 USER_ROLE ur
ON ( r.id = ur.rid)
INNER JOIN
 USER u
ON (ur.uid = u.id);

3)编写实体类

public class Role implements Serializable {
    private Integer roleId;
    private String roleName;
    private String roleDesc;
  
    //多对多的关系映射:一个角色可以赋予多个用户
    private List<User> users;
  
    public List<User> getUsers() {
        return users;
    }
    public void setUsers(List<User> users) {
        this.users = users;
    }
    public Integer getRoleId() {
        return roleId;
    }
    public void setRoleId(Integer roleId) {
        this.roleId = roleId;
    }
    public String getRoleName() {
        return roleName;
    }
    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }
    public String getRoleDesc() {
        return roleDesc;
    }
    public void setRoleDesc(String roleDesc) {
        this.roleDesc = roleDesc;
    }
  
    @Override
    public String toString() {
        return "Role{" +
                "roleId=" + roleId +
                ", roleName='" + roleName + '\'' +
                ", roleDesc='" + roleDesc + '\'' +
                '}';
    }
}

4)编写Dao层

public interface RoleDao {
    //查询所有角色
    List<Role> findAll();
}

5)配置文件

<mapper namespace="com.atguigu.dao.RoleDao">
    <resultMap id="roleMap" type="role">
        <id property="roleId" column="rid"></id>
        <result property="roleName" column="role_name"></result>
        <result property="roleDesc" column="role_desc"></result>
      
        <collection property="users" ofType="user">
            <id column="id" property="id"></id>
            <result column="username" property="username"></result>
            <result column="address" property="address"></result>
            <result column="sex" property="sex"></result>
            <result column="birthday" property="birthday"></result>
        </collection>
    </resultMap>
  
    <select id="findAll" resultMap="roleMap">
        select u.*,r.id as rid,r.role_name,r.role_desc
        from role r
                 left outer join user_role ur on r.id = ur.rid
                 left outer join user u on u.id = ur.uid
    </select>
</mapper>

6)测试类

public class Test1 {
    private InputStream is;
    private SqlSession session;
    private RoleDao roleDao;
  
    @Before
    public void init()throws Exception {
        is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        session = factory.openSession();
        roleDao = session.getMapper(RoleDao.class);
    }
    @After
    public void destory()throws Exception{
        session.commit();//默认自动提交为false
        session.close();
        is.close();
    }
    @Test
    public void test(){
        List<Role> all = roleDao.findAll();
        for (Role role:all){
            System.out.println(role);
        }
    }
}

Mybatis后续的学习:
mybatis 概述 | 配置文件详解:https://blog.csdn.net/weixin_45606067/article/details/107368570
mybatis延迟加载 | 缓存机制详解:https://blog.csdn.net/weixin_45606067/article/details/107368706
mybatis 注解开发版:https://blog.csdn.net/weixin_45606067/article/details/107368743
mybatis 逆向工程的使用:https://blog.csdn.net/weixin_45606067/article/details/107368781
pageHelper分页技术:https://blog.csdn.net/weixin_45606067/article/details/107368847


如果有收获!!! 希望老铁们来个三连,点赞、收藏、转发
创作不易,别忘点个赞,可以让更多的人看到这篇文章,顺便鼓励我写出更好的博客

猜你喜欢

转载自blog.csdn.net/weixin_45606067/article/details/107368642