今日内容
1.mybatis中的连接池以及事务控制 ---- 原理了解,会用即可
mybatis中连接池使用及分析
mybatis事务控制的分析
2. mybatis基于XML配置的动态SQL语句使用 ---- 会用即可
mappers配置文件中的几个标签
<if>
<where>
<foreach>
<sql>
3. mybatis中的多表操作 ----- 掌握应用
一对多
一对一
多对多
1.连接池
1.1 连接池概述
我们在实际开发中都会使用连接池,因为它可以减少我们获取连接所消耗的时间。
连接池就好比一个容器一样,把连接都初始化出来放到一个容器中。
我们从容器中取一个用一个,那么怎么取就成为一个很关键的事情?
为什么要一开始就初始化这么多连接?到的时候再初始化一个不行吗?
因为我们在实际开发中,获取连接的操作是很费时的。
* 连接池
连接池就是用于存储连接的一个容器,这个容器是一个集合对象,而且该集合对象必须是线程安全的,
不能两个线程拿到同一个连接。
该集合还必须实现队列的特性:先进先出。
而且连接用完之后,还会返回给连接池。
1.2 mybatis中的连接池
mybatis连接池提供了三种方式的配置:
* 配置的位置:
主配置文件SqlMapConfig.xml中的dataSource标签
type属性表示采用何种连接池方式
* Mybatis连接池的分类
POOLED:采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现。
使用连接池的数据源
UNPOOLED:采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。
意味着没有池的思想,每次获取连接都要重新初始化一个连接。
不使用连接池的数据源。
JNDI:采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到的DataSource是不一样。
使用JNDI实现的数据源。
注意:如果不是web或者maven的war工程,是不能使用的。
我们课程中使用的是tomcat服务器,采用的连接池就是dabcp连接池
* Mybatis中和连接池有关的相应的类
package javax.sql;
这个就是JDBC规范中的连接池定义,这个连接池定义里面一定有一个方法:
Connection getConnection()
package org.apache.ibatis.datasource.pooled;
会重写Connection getConnection(){}方法
package org.apache.ibatis.datasource.unpooled;
会重写Connection getConnection(){}方法。
走的还是注册驱动,获取连接这一套。
```
<configuration>
<environments default="mysql">
<environment id="mysql">
<!--配置事务-->
<transactionManager type="JDBC"></transactionManager>
<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>
</environment>
</environments>
</configuration>
```
1.3 mybatis提供连接池的思想
1.3.1 UNPOOLED
* 采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。
意味着没有池的思想,每次获取连接都要重新初始化一个连接,不使用连接池的数据源。
* 走的还是注册驱动,获取连接这一套。
1.3.2 POOLED
* 采用的是javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现。
使用连接池的数据源
* Mybatis提供POOLED连接池的过程
* 既没有用c3p0也没有druid等,而是自己实现了一套连接池的思想。也是使用的jdbc规范中的javax.sql.datasource;
接口而实现的方式
1.4. mybatis中的事务
什么是事务
事务的四大特性ACID
不考虑隔离性会产生的3个问题
解决办法:四种隔离级别
(在面试的时候要能达的上来)
mybatis中的事务,是通过sqlsession对象的commit方法和rollback方法实现事务的提交和回滚。
而且走到最后,还是走到connection.commit/connection.rollback
* Transaction - Setting autocommit to false :
Transaction将自动提交设为false,导致我们每次要自己手动提交sqlsession.commit
那么如何实现自动提交呢?
我们发现在public interface SqlSessionFactory中的 SqlSession openSession()方法另一种重写形式:
所以我们在创建SqlSession对象时,用的是factory.openSession(true),就可以实现自动提交事务。
2. mybatis基于XML配置的动态SQL语句使用
mybatis支持一些动态的sql语句,这些语句都是和查询相关的。我们在写查询的时候,会面临一些查询条件。
这些条件有可能有也可能没有,而且条件的数量也是不固定的。那么我们怎么设定这种sql语句呢?
2.1 动态SQL之标签
* <if>标签就可以根据不同的情况帮我们拼接查询条件。
```
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
<!--根据条件查询-->
<select id="findUserByCondition" resultType="com.itheima.domain.User" parameterType="com.itheima.domain.User">
select * from user where 1 = 1
<!--判断传入的参数user中是否有username属性-->
<if test="username != null">
and username like #{username}
</if>
<!--判断传入的参数user中是否有sex属性-->
<if test="sex != null">
and sex = #{sex}
</if>
<if test="address != null">
and address = #{address}
</if>
<if test="id != null">
and id = #{id}
</if>
<if test="birthday != null">
and birthday = #{birthday}
</if>
</select>
```
* 这在一定程度上达到了动态SQL的目的,但是不好的一点就是:如果实体类有许多许多属性,那么就要写许多许多<if>标签。
2.2 动态SQL之标签 : 不用写where 1 = 1
将where 1 = 1去掉了
```
<select id="findUserByCondition" resultType="com.itheima.domain.User" parameterType="com.itheima.domain.User">
select * from user
<where>
<if test="username != null">
and username like #{username}
</if>
<if test="sex != null">
and sex = #{sex}
</if>
<if test="address != null">
and address = #{address}
</if>
<if test="id != null">
and id = #{id}
</if>
<if test="birthday != null">
and birthday = #{birthday}
</if>
</where>
</select>
```
2.3 动态SQL之标签
解决这种子查询:select * from user where id in(41, 42, 43, 45)
```
<!--根据QueryVo中提供的id集合,查询用户信息-->
<select id="findUserInIds" resultType="com.itheima.domain.User" parameterType="com.itheima.domain.QueryVo">
select * from user
<where>
<if test="ids != null and ids.size() > 0">
<foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
<!--和item="uid"要一致-->
#{uid}
</foreach>
</if>
</where>
</select>
```
3. mybatis中的多表查询
表之间的关系有几种:
一对多
多对一
一对一
多对多
举例:
一个用户可以下多个订单 : 一对多
多个订单属于同一个用户 : 多对一
一个人只能有一张身份证 : 一对一
多个学生可以被多个老师教过 : 多对多
多个老师可以教多个学生
特例:
如果拿出每一个订单,他都属于每一个用户
所以Mybatis就把多对一看成了一对一。
mybatis中的多表查询:
示例:用户和账户
一个用户可以有多个账户
一个账户只能属于一个用户(多个账户也可以属于同一个用户)
步骤:
1.建立两张表:用户表,账户表
让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加
2.建立两个实体类:用户实体类和账户实体类
让用户和账户的实体类能体现出来一对多的关系
3.建立两个配置文件
用户的配置文件
账户的配置文件
4.实现配置
当我们查询用户时,可以同时得到用户下所包含的账户信息
当我们查询账户时,可以同时得到账户的所属用户信息。
3.1 需求 查询所有账户,并且带有所属用户的用户名称和地址信息
3.1.1 代码实现1:不常用的方式
* sql:select a.*, u.username, u.address from user u, account a where u.id = a.uid
```
* 1.Account表对应的实体类
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
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 +
'}';
}
}
* 2.查询得到的所属用户的用户名称和地址信息封装成到:AccountUser类
public class AccountUser extends Account {
//AccountUser继承Account只是为了能打印出Account表的信息
//将查询得到的username和address封装到这个类中
private String username;
private String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return super.toString() + " AccountUser{" +
"username='" + username + '\'' +
", address='" + address + '\'' +
'}';
}
}
3. IAccountDao
List<AccountUser> findAllAccount();
4. AccountTest
/**
* 测试查询所有账户,并且带有所属用户的用户名称和地址信息
* select a.*, u.username, u.address from user u, account a where u.id = a.uid
* @return
*/
@Test
public void testFindAllAccountUser(){
List<AccountUser> accountUsers = accountDao.findAllAccount();
for (AccountUser accountUser : accountUsers) {
System.out.println(accountUser);
}
}
5.IAccountDao.xml
<!--查询所有账户,并且带有用户名称和地址信息-->
<select id="findAllAccount" resultType="com.itheima.domain.AccountUser">
select a.*, u.username, u.address from user u, account a where u.id = a.uid
</select>
```
3.1.2 代码实现2:常用的方式
写一个映射标签<resultMap>
<resultMap id="accountUserMap" type="com.itheima.domain.Account">
<!--主键字段的对应-->
<!--这里account的id列在下面的sql中起了别名,所以这里也用别名-->
<id property="id" column="aid"></id>
<!--非主键字段的对应-->
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!--一对一的关系映射:配置封装user表中的信息-->
<!--javaType指明信息封装到哪个对象-->
<association property="user" column="uid" javaType="com.itheima.domain.User">
<id property="id" column="id"></id>
<result column="username" property="username"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>
再把<resultMap>标签放到<select>标签里面。
* 从表实体应该包含一个主表实体的对象引用
使用resultMap在IAccountDao.xml定义专门的resultMap用于映射一对一查询结果
我们可以在Account类中加入一个User类的对象来代表这个账户是哪个用户的。
```
* 1.Account表对应的实体类
private Integer id;
private Integer uid;
private Double money;
//从表实体应该包含一个主表实体的对象引用
private User user;
* 2.IAccountDao
List<AccountUser> findAllAccount();
* 3.IAccountDao.xml
<resultMap id="accountUserMap" type="com.itheima.domain.Account">
<!--主键字段的对应-->
<!--这里account的id列在下面的sql中起了别名,所以这里也用别名-->
<id property="id" column="aid"></id>
<!--非主键字段的对应-->
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!--一对一的关系映射:配置封装user表中的信息-->
<!--javaType指明信息封装到哪个对象-->
<association property="user" column="uid" javaType="com.itheima.domain.User">
<id property="id" column="id"></id>
<result column="username" property="username"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>
<!--查询所有方法-->
<select id="findAll" resultMap="accountUserMap">
<!--因为这里的结果:user表中出现了id列,account表中也有id列,所以我们为其中一个id列起别名a.id as aid-->
select u.*, a.id as aid, a.uid, a.money from account a, user u where u.id = a.uid;
</select>
```
3.2 一对多
需求:查询所有用户,同时获取用户下所有账户的信息
1.SQL语句:不能用内连接,要用左外连接(将LEFT OUTER JOIN左边的数据都查询出来)。
内连接:必行
SELECT * FROM USER u, account a WHERE u.id = a.uid
左外连接:
SELECT u.*, a.`ID` AS aid, a.`MONEY`, a.`UID` FROM USER u LEFT OUTER JOIN account a ON u.id = a.`UID`
或者右连接也行:
SELECT a.`ID` AS aid, a.`MONEY`, a.`UID`, u.* FROM account a RIGHT OUTER JOIN USER u ON u.id = a.`UID`
2. User实体类
//一对多关系映射:每个用户可能有多个账户
//主表实体应该包含从表实体的集合引用(因为是一对多)
private List<Account> accounts;
```
<!--定义User的resultMap-->
<resultMap id="userAccountMap" type="com.itheima.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<result property="birthday" column="birthday"></result>
<!--配置 user对象中account集合的映射-->
<collection property="accounts" ofType="com.itheima.domain.Account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
<!--查询所有方法-->
<select id="findAll" resultMap="userAccountMap">
SELECT u.*, a.id as aid, a.uid, a.money FROM USER u LEFT OUTER JOIN account a ON u.id = a.`UID`
</select>
```
3.3 多对多关系
示例:用户和角色
一个用户可以有多个角色:用户老王可以是院长角色也可以是总裁角色
一个角色可以赋予多个用户:可以同时有多个院长角色:老王和老李
步骤:
1.建立两张表:用户表,角色表
让用户表和角色表具有多对多的关系。
数据库中实现多对多的关系,需要使用中间表,中间表中包含各自的主键,在中间表中是外键。
2.建立两个实体类:用户实体类和角色实体类
让用户和角色的实体类能体现出多对多的关系
各自包含对方一个集合引用
3.建立两个配置文件
用户的配置文件
角色的配置文件
4.实现配置
当我们查询用户时,可以同时得到用户所包含的角色信息
当我们查询角色时,可以同时得到角色的所赋予的用户信息。
3.3.1 从角色表查,得到全部的角色的信息,同时得到所赋予的用户信息。
思路:
* 在完善一下想要得到的结果:
sql:
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 ur.uid = u.id
3.3.2 从用户表查,得到全部的用户信息,并同时得到用户所包含的角色信息
并不是所有用户都带着角色:有角色带着角色,没有就null
思路:同4.1一样,所以只要sql改一下就行了
sql:
SELECT u.*, r.id AS rid, r.role_name, r.role_desc FROM USER u LEFT OUTER JOIN user_role ur ON u.id = ur.uid LEFT OUTER JOIN role r ON r.id = ur.rid
4.JNDI
概述:Java Naming and Directory Interface。
是SUN公司推出的一套规范,属于JavaEE技术之一。目的是模仿windows系统中的注册表。
示例:在服务器中注册数据源
1.他在我们这里边是基于tomcat服务器的:在服务器中注册数据源
2.当tomcat一启动(所以必须是war工程),也会为我们准备这样一个map:那我们按照规定向里面写一写东西即可
key:字符串
directory:固定的
name:可以指定的
value:Object
存放什么对象是我们自己制定的,指定的方式是通过配置文件的方式。/webapp/META-INF/context.xml
3.JNDI是以这种形式存放的(map:键值对)
4.这里我们SqlMapConfig.xml:也有相应的改变
以前是引入外部连接的配置,
改成直接指定数据源:配置在context.xml
原来我们是通过写在外部的properties.xml文件读取的,现在我们是通过指定一个dataSource,
并且value就是context.xml对应的路径+名称(url+name)
思考: 在java工程中能不能实现这个功能呢?
很显然是不能的,只能在war(web)工程。
想用JNDI那么必须做下面这些:
1.创建一个mavenweb工程
2.在webapp目录下创建META-INF目录,并在里面创建配置文件 context.xml
context.xml:
```
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<!--
<Resource
name="jdbc/eesy_mybatis" 数据源的名称
type="javax.sql.DataSource" 数据源类型
auth="Container" 数据源提供者
maxActive="20" 最大活动数
maxWait="10000" 最大等待时间
maxIdle="5" 最大空闲数
username="root" 用户名
password="1234" 密码
driverClassName="com.mysql.jdbc.Driver" 驱动类
url="jdbc:mysql://localhost:3306/eesy_mybatis" 连接url字符串
/>
-->
<Resource
name="jdbc/txl_mybatis" --------------- 是我们自己指定的名称
type="javax.sql.DataSource" --------------- 要存的对象,这里指定是一个数据源
auth="Container" -------------- 提供者(作者),由容器来提供数据源,这里指的是tomcat
maxActive="20" ------------- 最大活动链接
maxWait="10000" -------------- 最大等待时长
maxIdle="5" -------------- 最大空闲数
username="root"
password="1234"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/eesy_mybatis"
/>
</Context>
```
3. SqlMapConfig.xml:也有相应的改变
1.SqlMapConfig.xml(原来的)
<configuration>
<properties resource="jdbcConfig.properties"></properties>
<typeAliases>
<package name="com.itheima.domain"/>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql的环境-->
<environment id="mysql">
<!--配置事务-->
<transactionManager type="JDBC"></transactionManager>
<dataSource type="UNPOOLED">
<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>
</environment>
</environments>
<!--映射文件的配置-->
<mappers>
<package name="com.itheima.Dao"/>
</mappers>
</configuration>
2.SqlMapConfig.xml(JNDI)
<configuration>
<typeAliases>
<package name="com.itheima.domain"></package>
</typeAliases>
<!-- 配置mybatis的环境 -->
<environments default="mysql">
<!-- 配置mysql的环境 -->
<environment id="mysql">
<!-- 配置事务控制的方式 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的必备信息 type属性表示是否使用数据源(连接池)-->
<dataSource type="JNDI">
<property name="data_source" value="java:comp/env/jdbc/txl_mybatis"/>
</dataSource>
</environment>
</environments>
<!-- 指定mapper配置文件的位置 -->
<mappers>
<mapper resource="com/itheima/dao/IUserDao.xml"/>
</mappers>
</configuration>
标红的是不能改的:
4.将测试文件 写在jsp中
注意:jsp中SqlSession对象的变量名不能起session,因为jsp中有一个隐式对象也是session
```
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<body>
<h2>Hello World!</h2>
<%
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
SqlSession sqlSession = factory.openSession();
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
in.close();
%>
</body>
</html>
```
5.为什么在单元测试中用不了,必须放在jspo中访问呢?
当我们访问jsp时,经过了tomcat服务器,jsp在执行时先翻译成.java再编译成.class再去运行,这些都是tomcat来做的。
当你经过了tomcat服务器,这时候tomcat服务器为你准备的数据源就可以使用的。
而如果用单元测试,根本没有经过tomcat,就是一个java的程序在运行。那么此时tomcat为我们准备的数据源,就不能使用。
这就是JNDI数据源的使用。