1.JDBC缺点及解决方案
2.MyBatis介绍
2.2.官方文档
2.3.特点
Mybatis:
1)支持自定义SQL、存储过程、及高级映射
2)实现自动对SQL的参数设置
3)实现自动对结果集进行解析和封装
4)通过XML或者注解进行配置和映射,大大减少代码量
5)数据源的连接信息通过配置文件进行配置
可以发现,MyBatis是对JDBC进行了简单的封装,帮助用户进行SQL参数的自动设置,以及结果集与Java对象的自动映射。
与Hibernate相比,配置更加简单、灵活、执行效率高。但是正因为此,所以没有实现完全自动化,需要手写SQL,这是优点也是缺点。
对性能要求较高的电商类项目,一般会使用MyBatis,而对与业务逻辑复杂,不太在乎执行效率的传统行业,一般会使用Hibernate.
2.4.Mybaits整体架构
1、配置文件
全局配置文件:mybatis-config.xmlhibernate.cfg.xml,作用:配置数据源,引入映射文件
映射文件:XxMapper.xmlxx.hbm.xml,作用:配置sql语句、参数、结果集封装类型等
2、SqlSessionFactory
相当于Hibernate的SessionFactory,作用:获取SqlSession
通过newSqlSessionFactoryBuilder().build(inputStream)来构建,inputStream:读取配置文件的IO流
3、SqlSession
相当于Hibernate的Session,作用:执行CRUD操作
4、Executor
执行器,SqlSession通过调用它来完成具体的CRUD
5、Mapped Statement
在映射文件里面配置,包含3部分内容:具体的sql,sql执行所需的参数类型,sql执行结果的封装类型
参数类型和结果集封装类型包括3种:HashMap,基本数据类型,pojo
3. 快速入门
3.1.目录结构
3.2.引入依赖(pom.xml)
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.43</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
</dependencies>
3.3.全局配置文件(mybatis-config.xml)
Mybatis-config.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>
<!-- 环境:说明可以配置多个,default:指定生效的环境 -->
<environments default="development">
<!-- id:环境的唯一标识 -->
<environment id="development">
<!-- 事务管理器,type:类型 -->
<transactionManager type="JDBC" />
<!-- 数据源:type-池类型的数据源 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis-49" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- 映射文件 -->
<mappers>
<mapper resource="UserMapper.xml" />
</mappers>
</configuration>
3.4.映射文件(UserMapper.xml)
UserMapper.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">
<!-- namespace(命名空间):映射文件的唯一标识 -->
<mapper namespace="UserMapper">
<!-- 查询的statement,id:在同一个命名空间下的唯一标识,resultType:sql语句的结果集封装类型 -->
<select id="queryUserById" resultType="User">
select * from tb_user where id=#{id}
</select>
</mapper>
3.5.编写mybatis程序(MybatisTest.java)
public static void main(String[] args) throws IOException {
SqlSession sqlSession = null;
try {
// 指定mybatis的全局配置文件
String resource = "mybatis-config.xml";
// 读取mybatis-config.xml配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取sqlSession回话
sqlSession = sqlSessionFactory.openSession();
// 执行查询操作,获取结果集。参数:1-命名空间(namespace)+“.”+statementId,2-sql的占位符参数
User user = sqlSession.selectOne("UserMapper.queryUserById", 1l);
System.out.println(user);
} finally {
// 关闭连接
if (sqlSession != null) {
sqlSession.close();
}
}
}
4.引入log日志
打印日志2个步骤:
1、在pom.xml中,引入slf4j的依赖
2、在src/main/resources目录下添加log4j.properties文件
4.1.引入日志依赖包(pom.xml)
4.2.添加log4j.properties
log4j.rootLogger=DEBUG,A1
log4j.logger.org.apache=DEBUG
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
4.3.日志输出
控制台截图
4.4.MyBatis使用步骤总结
1)配置mybatis-config.xml 全局的配置文件 (1、数据源,2、外部的mapper)
2)创建SqlSessionFactory
3)通过SqlSessionFactory创建SqlSession对象
4)通过SqlSession操作数据库CRUD
5)调用session.commit()提交事务
6)调用session.close()关闭会话
5.完整的CRUD操作
5.1.创建UserDao接口
public interface UserDao {
/**
* 根据id获取用户信息
* @param id
* @return
*/
public User queryUserById(Long id);
/**
* 查询所有用户
* @return
*/
public List<User> queryUserAll();
/**
* 新增用户
* @param user
*/
public void insertUser(User user);
/**
* 更新用户信息
* @param user
*/
public void updateUser(User user);
/**
* 根据id删除用户信息
* @param id
*/
public void deleteUserById(Long id);
}
5.2.创建UserDaoImpl
public class UserDaoImpl implements UserDao {
private SqlSession sqlSession;
public UserDaoImpl(SqlSession sqlSession){
this.sqlSession = sqlSession;
}
@Override
public User queryUserById(Long id) {
return this.sqlSession.selectOne("UserDaoMapper.queryUserById", id);
}
@Override
public List<User> queryUserAll() {
return this.sqlSession.selectList("UserDaoMapper.queryUserAll");
}
@Override
public void insertUser(User user) {
this.sqlSession.insert("UserDaoMapper.insertUser", user);
this.sqlSession.commit();
}
@Override
public void updateUser(User user) {
this.sqlSession.update("UserDaoMapper.updateUser", user);
this.sqlSession.commit();
}
@Override
public void deleteUserById(Long id) {
this.sqlSession.delete("UserDaoMapper.deleteUserById", id);
this.sqlSession.commit();
}
}
5.3.编写UserDaoMapper.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="UserDaoMapper">
<select id="queryUserById" resultType="cn.itcast.mybatis.pojo.User">
select * from tb_user where id = #{id}
</select>
<select id="queryUserAll" resultType="cn.itcast.mybatis.pojo.User">
select * from tb_user
</select>
<insert id="insertUser" parameterType="cn.itcast.mybatis.pojo.User">
INSERT INTO tb_user (
user_name,
password,
name,
age,
sex,
birthday,
created,
updated
)
VALUES
(
#{userName},
#{password},
#{name},
#{age},
#{sex},
#{birthday},
NOW(),
NOW()
);
</insert>
<update id="updateUser" parameterType="cn.itcast.mybatis.pojo.User">
UPDATE tb_user
SET
user_name = #{userName},
password = #{password},
name = #{name},
age = #{age},
sex = #{sex},
birthday = #{birthday},
updated = NOW()
WHERE
(id = #{id});
</update>
<delete id="deleteUserById" parameterType="java.lang.Long">
delete from tb_user where id=#{id}
</delete>
</mapper>
5.4.引入UserDaoMapper.xml
在mybatis-config.xml中引入UserDaoMapper.xml映射文件:
5.5.测试UserDao
1、引入Junit依赖:参照父工程,在mybatis工程中的pom.xml中引入junit的依赖
2、创建junit测试用例:右键UserDao—>New—>Junit Test Case
3、编写测试用例:
public class UserDaoTest {
private UserDao userDao;
@Before
public void setUp() throws Exception {
String resource = "mybatis-config.xml";
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 初始化userDao
this.userDao = new UserDaoImpl(sqlSession);
}
@Test
public void testQueryUserById() {
User user = this.userDao.queryUserById(1l);
System.out.println(user);
}
@Test
public void testQueryUserAll() {
List<User> users = this.userDao.queryUserAll();
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testInsertUser() {
User user = new User();
user.setAge(18);
user.setName("柳岩");
user.setPassword("123456");
user.setUserName("yanyan");
user.setSex(3);
user.setBirthday(new Date());
this.userDao.insertUser(user);
}
@Test
public void testUpdateUser() {
// 查询
User user = this.userDao.queryUserById(7l);
// 更新
user.setAge(28);
user.setPassword("111111");
this.userDao.updateUser(user);
}
@Test
public void testDeleteUserById() {
this.userDao.deleteUserById(7l);
}
}
5.6.总结
5.6.总结
1、 编写UserDao接口
2、 编写UserDao的实现类UserDaoImpl及映射文件UserDaoMapper.xml
3、 修改全局配置文件,引入UserDaoMapper.xml
4、 编写UserDao的Junit Test Case测试用例
5.7.解决UserName为null
查询数据的时候,查不到userName的信息,原因:数据库的字段名是user_name POJO中的属性名是userName
两端不一致,造成mybatis无法填充对应的字段信息。修改方法:在sql语句中使用别名
解决方案1:在sql语句中使用别名
修改映射文件(UserDaoMapper.xml)中的sql语句
控制台Log日志:
6.动态代理Mapper实现类
6.1.思考CRUD中的问题
1、接口->实现类->mapper.xml。
2、实现类中,使用mybatis的方式非常类似。
3、sql statement 硬编码到java代码中。
思考:能否只写接口,不书写实现类,只编写Mapper.xml即可
因为在dao(mapper)的实现类中对sqlsession的使用方式很类似。mybatis提供了接口的动态代理.
Mapper接口的动态代理实现,需要满足以下条件:
1.映射文件中的命名空间与Mapper接口的全路径一致
2.映射文件中的statementId与Mapper接口的方法名保持一致
3.映射文件中的statement的ResultType必须和mapper接口方法的返回类型一致(即使不采用动态代理,也要一致)
4.映射文件中的statement的parameterType必须和mapper接口方法的参数类型一致(不一定,该参数可省略)
6.2.使用动态代理改造CRUD
在mybatis中,持久层的XxxDao通常习惯上命名为XxxMapper(例如:以前命名为UserDao的接口,现在命名为UserMapper)。
当然,仍有少部分项目会保留Dao的命名,以后见到Mapper或者Dao的命名都不要奇怪。
采用动态代理之后,只剩下UserMapper接口、UserMapper.xml映射文件以及UserMapper接口的测试文件**,即可以少写一个接口的实现类.
6.2.1.创建UserMapper接口
public interface UserMapper {
/**
* 根据id获取用户信息
* @param id
* @return
*/
public User queryUserById(Long id);
/**
* 查询所有用户
* @return
*/
public List<User> queryUserAll();
/**
* 新增用户
* @param user
*/
public void insertUser(User user);
/**
* 更新用户信息
* @param user
*/
public void updateUser(User user);
/**
* 根据id删除用户信息
* @param id
*/
public void deleteUserById(Long id);
}
6.2.2.UserMapper映射文件
名称空间必须改成UserMapper接口的全路径,StatementId必须和接口方法名一致,结果集的封装类型已经和方法的返回类型一致
<?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.itcast.mybatis.mapper.UserMapper">
<select id="queryUserById" resultType="cn.itcast.mybatis.pojo.User">
select * from tb_user where id = #{id}
</select>
<select id="queryUserAll" resultType="cn.itcast.mybatis.pojo.User">
select * from tb_user
</select>
<insert id="insertUser" parameterType="cn.itcast.mybatis.pojo.User">
INSERT INTO tb_user (
user_name,
password,
name,
age,
sex,
birthday,
created,
updated
)
VALUES
(
#{userName},
#{password},
#{name},
#{age},
#{sex},
#{birthday},
NOW(),
NOW()
);
</insert>
<update id="updateUser" parameterType="cn.itcast.mybatis.pojo.User">
UPDATE tb_user
SET
user_name = #{userName},
password = #{password},
name = #{name},
age = #{age},
sex = #{sex},
birthday = #{birthday},
updated = NOW()
WHERE
(id = #{id});
</update>
<delete id="deleteUserById" parameterType="java.lang.Long">
delete from tb_user where id=#{id}
</delete>
</mapper>
6.2.3.UserMapperTest测试
public class UserMapperTest {
private UserMapper userMapper;
@Before
public void setUp() throws Exception {
// 读取mybatis的全局配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 构建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取sqlSession会话
SqlSession sqlSession = sqlSessionFactory.openSession();
// 初始化userDao
this.userMapper = sqlSession.getMapper(UserMapper.class);
}
@Test
public void testQueryUserAll() {
List<User> userList = this.userMapper.queryUserAll();
for (User user : userList) {
System.out.println(user);
}
}
}
1、如果名称空间不和mapper接口的全路径保持一致
7.mybatis-config.xml配置
mybatis-config.xml讲究严格的顺序,具体顺序遵循文档的顺序
7.1.properties属性读取外部资源
添加jdbc.properties资源文件:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mybatis-44
username=root
password=root
在Mybatis-config.xml中引入jdbc.properties资源文件:
引入外部资源文件,resource:相对路径,url:绝对路径
<?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>
<!-- 引入外部资源文件,resource:相对路径,url:绝对路径 -->
<properties resource="jdbc.properties" />
<!-- 环境:说明可以配置多个,default:指定生效的环境 -->
<environments default="development">
<!-- id:环境的唯一标识 -->
<environment id="development">
<!-- 事务管理器,type:类型 -->
<transactionManager type="JDBC" />
<!-- 数据源:type-池类型的数据源 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
<!-- 映射文件 -->
<mappers>
<mapper resource="UserMapper.xml" />
<mapper resource="UserDaoMapper.xml" />
</mappers>
</configuration>
通过properties引入外部资源文件之后,就可以通过${xxx}的方式使用资源文件里的参数了。
7.2.settings设置
settings参数有很多,重点学习这4个参数。
开启驼峰匹配:
完成经典的数据库命名到java属性的映射。
经典数据库命名:如果多个单词之间,通常使用下划线进行连接。
java中命名:第二个单词首字母大写。
驼峰匹配:相当于去掉数据中的名字的下划线,和java进行匹配。
问题:查询数据的时候,查不到userName的信息,原因:数据库的字段名是user_name,POJO中的属性名字是userName,两端不一致,造成mybatis无法填充对应的字段信息。修改方法:在sql语句中使用别名。
解决方案1:在sql语句中使用别名
解决方案2:参考驼峰匹配 — mybatis-config.xml
<settings>
<!-- 开启驼峰匹配:经典的数据库列名(多个单词下划线连接)映射到经典的java属性名(多个单词驼峰连接) -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
7.3.typeAliases
问题:之前咱们在映射文件中用到java类型时,都是使用类的全路径,书写起来非常麻烦。
解决方案:类型别名是为 Java 类型命名的一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。(官方文档)
7.3.1.方式一:typeAlias 缺点:每个pojo类都要去配置。
<typeAliases>
<!-- 类型别名:type-pojo类的全路径,alias-别名名称(可随便写,推荐和类名一致) -->
<typeAlias type="cn.itcast.mybatis.pojo.User" alias="user" />
</typeAliases>
7.3.2.方式二:package
扫描指定包下的所有类,扫描之后的别名就是类名,大小写不敏感(不区分大小写),建议使用的时候和类名一致。
<typeAliases>
<!-- 类型别名:type-pojo类的全路径,alias-别名名称(可随便写,推荐和类名一致) -->
<!-- <typeAlias type="cn.itcast.mybatis.pojo.User" alias="user" /> -->
<!-- 开启别名包扫描,name:包路径,扫描的别名就是类名,并且大小写不敏感 -->
<package name="cn.itcast.mybatis.pojo"/>
</typeAliases>
在映射文件中使用类型别名:
已经为普通的 Java 类型内建了许多相应的类型别名。它们都是大小写不敏感的,需要注意的是由于重载原始类型的名称所做的特殊处理。
7.4.typeHandlers(类型处理器)
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。
注:截图默认的类型处理器 不全,需要请查询相关资料。
7.5.plugins(插件又称拦截器)
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。
默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
7.6.environments(环境)
MyBatis 可以配置成适应多种环境,例如,开发、测试和生产环境需要有不同的配置;
尽管可以配置多个环境,每个 SqlSessionFactory 实例只能选择其一。
虽然,这种方式也可以做到很方便的分离多个环境,但是实际使用场景下,我们更多的是选择使用spring来管理数据源,来做到环境的分离。
7.6.1.方法一:default
添加一个test(测试)环境,并在default参数中指向test环境。
7.6.2.方法二:build方法
通过build方法的重载方法
使用:
7.7.Mappers
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要定义 SQL 映射语句了。但是首先我们需要告诉 MyBatis 到哪里去找到这些语句。
Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。
7.7.1.方式一:resource
在mybatis-config.xml引入项目目录下的映射文件:
7.7.2.方式二:file(不采用)
引入硬盘目录下的映射文件:
缺点:1、硬盘的位置可能随着项目的部署或迁移,路径发生变化
2、每新增一个映射文件,就要在全局配置文件中引入。
7.7.3.方式三:class
在mybatis-config.xml配置mapper接口的全路径:
这种配置方式,在全局配置文件中配置了mapper接口的全路径,并没有配置mapper接口的映射文件的位置。。如果要让mybatis找到对应的映射文件,则必须满足一定的条件或规则:
1、映射文件和mapper接口在同一个目录下
2、文件名必须一致
3、映射文件的namespace必须和mapper接口的全路径保持一致
目录结构:
缺点:
1、java文件和xml映射文件耦合
2、每新增一个映射文件,就要在全局配置文件中引入
7.7.4.方式四:package
在mybatis-config.xml中,开启包扫描:
原理:扫描目标包目录下的mapper接口,并按照class的方式找到接口对应的映射文件。
缺点:
1、如果包的路径有很多
2、mapper.xml和mapper.java没有分离。
8.Mapper XML 文件(映射文件)
8.1.CRUD标签
8.1.1.select
8.1.2.insert
8.1.3.update
8.1.4.delete
8.2.parameterType传入参数
CRUD标签都有一个属性parameterType,statement通过它指定接收的参数类型。
接收参数的方式有两种:
1、#{}预编译
2、${}非预编译(直接的sql拼接,不能防止sql注入)
参数类型有三种:
1、基本数据类型
2、HashMap(使用方式和pojo类似)
3、Pojo自定义包装类型
**8.2.1.
的
用
法
∗
∗
场
景
:
数
据
库
有
两
个
一
模
一
样
的
表
。
历
史
表
,
当
前
表
查
询
表
中
的
信
息
,
有
时
候
从
历
史
表
中
去
查
询
数
据
,
有
时
候
需
要
去
新
的
表
去
查
询
数
据
。
希
望
使
用
1
个
方
法
来
完
成
操
作
。
在
U
s
e
r
M
a
p
p
e
r
接
口
中
,
添
加
根
据
表
名
查
询
用
户
信
息
的
方
法
:
!
[
在
这
里
插
入
图
片
描
述
]
(
h
t
t
p
s
:
/
/
i
m
g
−
b
l
o
g
.
c
s
d
n
i
m
g
.
c
n
/
20190507164803591.
)
在
U
s
e
r
M
a
p
p
e
r
映
射
文
件
中
,
添
加
方
法
对
应
的
s
t
a
t
e
m
e
n
t
:
!
[
在
这
里
插
入
图
片
描
述
]
(
h
t
t
p
s
:
/
/
i
m
g
−
b
l
o
g
.
c
s
d
n
i
m
g
.
c
n
/
20190507164814841.
)
输
出
(
报
错
)
:
!
[
在
这
里
插
入
图
片
描
述
]
(
h
t
t
p
s
:
/
/
i
m
g
−
b
l
o
g
.
c
s
d
n
i
m
g
.
c
n
/
20190507164827749.
?
x
−
o
s
s
−
p
r
o
c
e
s
s
=
i
m
a
g
e
/
w
a
t
e
r
m
a
r
k
,
t
y
p
e
Z
m
F
u
Z
3
p
o
Z
W
5
n
a
G
V
p
d
G
k
,
s
h
a
d
o
w
1
0
,
t
e
x
t
a
H
R
0
c
H
M
6
L
y
9
i
b
G
9
n
L
m
N
z
Z
G
4
u
b
m
V
0
L
3
R
h
b
m
d
p
d
2
F
u
Z
w
=
=
,
s
i
z
e
1
6
,
c
o
l
o
r
F
F
F
F
F
F
,
t
7
0
)
如
果
你
要
动
态
传
入
的
字
段
名
是
表
名
,
并
且
s
q
l
执
行
是
预
编
译
的
,
这
显
然
是
不
行
的
,
所
以
你
必
须
改
成
非
预
编
译
的
,
也
就
是
这
样
:
!
[
在
这
里
插
入
图
片
描
述
]
(
h
t
t
p
s
:
/
/
i
m
g
−
b
l
o
g
.
c
s
d
n
i
m
g
.
c
n
/
20190507164858617.
)
注
意
:
1.
使
用
{}的用法** 场景:数据库有两个一模一样的表。历史表,当前表 查询表中的信息,有时候从历史表中去查询数据,有时候需要去新的表去查询数据。希望使用1个方法来完成操作。 在UserMapper接口中,添加根据表名查询用户信息的方法: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190507164803591.) 在UserMapper映射文件中,添加方法对应的statement: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190507164814841.) 输出(报错): ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190507164827749.?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Rhbmdpd2FuZw==,size_16,color_FFFFFF,t_70) 如果你要动态传入的字段名是表名,并且sql执行是预编译的,这显然是不行的,所以你必须改成非预编译的,也就是这样: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190507164858617.) 注意: 1.使用
的用法∗∗场景:数据库有两个一模一样的表。历史表,当前表查询表中的信息,有时候从历史表中去查询数据,有时候需要去新的表去查询数据。希望使用1个方法来完成操作。在UserMapper接口中,添加根据表名查询用户信息的方法:![在这里插入图片描述](https://img−blog.csdnimg.cn/20190507164803591.)在UserMapper映射文件中,添加方法对应的statement:![在这里插入图片描述](https://img−blog.csdnimg.cn/20190507164814841.)输出(报错):![在这里插入图片描述](https://img−blog.csdnimg.cn/20190507164827749.?x−oss−process=image/watermark,typeZmFuZ3poZW5naGVpdGk,shadow10,textaHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Rhbmdpd2FuZw==,size16,colorFFFFFF,t70)如果你要动态传入的字段名是表名,并且sql执行是预编译的,这显然是不行的,所以你必须改成非预编译的,也就是这样:![在这里插入图片描述](https://img−blog.csdnimg.cn/20190507164858617.)注意:1.使用{} 去接收参数信息,在一个参数时,默认情况下必须使用${value}获取参数值,
2.#{} 只是表示占位,与参数的名字无关,如果只有一个参数,可以使用任意参数名接收参数值,会自动对应。
但是这并不是一种稳妥的解决方案,推荐使用@Param注解指定参数名,所以以上接口及映射文件可以改成如下:
一个参数时,在使用#{}传参时,可以通过任意参数名接收参数;而${},默认必须通过value来接收参数。可以通过@Param注解指定参数名
8.2.2.多个参数
当mapper接口要传递多个参数时,有两种传递参数的方法:
1、默认规则获取参数{0,1,param1,param2}
2、使用@Param注解指定参数名
案例:实现一个简单的用户登录,根据username和password验证用户信息
在UserMapper.xml配置中,添加登陆方法对应的Statement配置:
在UserMapperTest测试用例中,添加测试方法:
运行报错:
注意报错信息:没有找到参数userName,可用的参数是:[1,0,param1,param2],所以可有以下解决方案:
解决方案一:
在映射文件里,通过#{0},#{1}获取参数
解决方案二:
在映射文件里,通过param1,param2获取参数
最终解决方案:
在接口方法中的参数前,添加@Param注解指定参数名
通常在方法的参数列表上加上一个注解@Param(“xxxx”) 表示参数的名字,然后通过${“xxxx”}或#{“xxxx”}获取参数。
注意:
单个参数时,#{}与参数名无关的。
多个参数时,#{} ${}与参数名(@Param)有关。
什么时候需要加@Param注解?什么时候不加?
单个参数不加,多个参数加;终极解决方案:都加。
8.2.3.HashMap
parameterType有三种类型的输入参数:
1、基本数据类型
2、hashMap
3、pojo包装类
hashMap这种类型的参数怎么传递参数呢?
它的使用方式和pojo有点类似,简单类型通过#{key}或者
k
e
y
,
复
杂
类
型
通
过
{key},复杂类型通过
key,复杂类型通过{key.属性名}或者#{key.属性名}
UserMapper接口方法:
UserMapper配置文件:
测试用例:
8.2.4.面试题(#、$区别)
练习:根据用户名进行模糊查询
UserMapper接口:
UserMapper映射文件:
8.3.resultMap
8.3.1.解决列名和属性名不一致
查询数据的时候,查不到userName的信息,原因:数据库的字段名是user_name,而POJO中的属性名字是userName
两端不一致,造成mybatis无法填充对应的字段信息。修改方法:在sql语句中使用别名。
解决方案1:在sql语句中使用别名
解决方案2:参考驼峰匹配 — mybatis-config.xml 的时候
解决方案3:resultMap自定义映射。
在UserMapper.xml中配置resultMap
在UserMapper.xml中使用resultMap:
8.3.2.resultMap的自动映射
在上面讲到的resultMap中,主键需要通过id子标签配置,表字段和属性名不一致的普通字段需要通过result子标签配置。
那么,字段名称匹配的字段要不要配置那?
这个取决于resultMap中的autoMapping属性的值:
为true时:resultMap中的没有配置的字段会自动对应。
为false时:只针对resultMap中已经配置的字段作映射。
并且resultMap会自动映射单表查询的结果集。
8.4.sql片段
在java代码中,为了提高代码的重用性,针对一些出现频率较高的代码,抽离出来一个共同的方法或者类
么针对一些重复出现的sql片段,mybatis有没有一个比较好的解决方案呢?
Mybatis当然想到了这一点,它就是sql标签。
8.4.1.用法一
sql标签可以定义一个sql片段,在需要使用该sql片段的地方,通过include标签来使用,如改造根据表名查询用户信息:
测试:
测试结果:
8.4.2.用法二
很多时候同一个sql片段,可能在很多映射文件里都有使用,这就需要添加一个映射文件,用来统一定义sql片段。
如下,在resource目录下新增CommonSQL.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">
<!-- namespace(命名空间):映射文件的唯一标识 -->
<mapper namespace="CommonSQL">
<sql id="commonSql">
id,user_name,
password,
name,
age,
sex,
birthday,
created,
updated
</sql>
</mapper>
定义好sql片段的映射文件之后,接下来就该使用它了,首先应该把该映射文件引入到mybatis的全局配置文件中(mybatis-config.xml):
最后在需要使用该sql片段的地方通过include标签的refId属性引用该sql片段:
在UserMapper.xml的映射文件中,进一步改造根据用户名查询用户信息
8.5.本章代码汇总
8.5.1.UserMapper.java
public interface UserMapper {
/**
* 根据用户名模糊查询用户信息
* @param userName
* @return
*/
public List<User> queryUsersLikeUserName(@Param("userName")String userName);
/**
* 登陆(Map的方式)
* @param map
* @return
*/
public User loginByMap(Map<String,Object> map);
/**
* 根据用户名和密码登陆
* @param userName
* @param password
* @return
*/
public User login(@Param("userName")String userName, @Param("password")String password);
/**
* 根据表名查询用户信息
* @param tableName
* @return
*/
public List<User> queryUsersByTableName(@Param("tableName")String tableName);
/**
* 根据id查询用户信息
* @param id
* @return
*/
public User queryUserById(Long id);
/**
* 查询所有用户信息
* @return
*/
public List<User> queryUserAll();
/**
* 新增用户信息
* @param user
*/
public void insertUser(User user);
/**
* 更新用户信息
* @param user
*/
public void updateUser(User user);
/**
* 根据id删除用户信息
* @param id
*/
public void deleteUserById(Long id);
}
8.5.2.UserMapper.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">
<!-- namespace(命名空间):映射文件的唯一标识 -->
<mapper namespace="cn.itcast.mybatis.mapper.UserMapper">
<!-- <sql id="commonSql">
id,user_name,
password,
name,
age,
sex,
birthday,
created,
updated
</sql> -->
<select id="queryUsersLikeUserName" resultType="User">
select <include refid="CommonSQL.commonSql"></include> from tb_user where user_name like '%' #{userName} '%'
</select>
<select id="loginByMap" resultType="User">
select * from tb_user where user_name=#{userName} and password=#{password}
</select>
<select id="login" resultType="User">
select * from tb_user where user_name=#{userName} and password=#{password}
</select>
<select id="queryUsersByTableName" resultType="User">
select * from ${tableName}
</select>
<!-- resultMap:自定义映射关系
属性:type-结果集的封装类型,id-唯一标识,autoMapping-开启自动匹配,如果开启了驼峰匹配,就以驼峰匹配的形式进行匹配
id:指定主键映射的,不要省。提高性能
result:其他的非主键普通字段
子标签的属性:Column-表中的字段名,property-对应的java属性名
-->
<resultMap type="User" id="userMap" autoMapping="true">
<id column="id" property="id"/>
<!-- <result column="user_name" property="userName"/> -->
</resultMap>
<!-- statement:查询的statement
id:在该映射文件下的唯一标识。在使用动态代理之后,必须和接口的方法名一致。必须属性
resultType:结果集的映射类型。在使用动态里之后,必须和接口方法的返回值类型一致。必须属性
parameterType:参数类型。可省略
-->
<select id="queryUserById" resultMap="userMap">
select * from tb_user where id = #{id}
</select>
<select id="queryUserAll" resultType="User">
select * from tb_user
</select>
<!-- 插入的statement
id:在该映射文件下的唯一标识。在使用动态代理之后,必须和接口的方法名一致。必须属性
parameterType:参数类型。可省略
useGeneratedKeys:开启主键回写,回写到参数的pojo对象里
keyColumn:主键列名
keyProperty:主键对应的属性名
-->
<insert id="insertUser" useGeneratedKeys="true" keyColumn="id" keyProperty="id" >
INSERT INTO tb_user (
user_name,
password,
name,
age,
sex,
birthday,
created,
updated
)
VALUES
(
#{userName},
#{password},
#{name},
#{age},
#{sex},
#{birthday},
now(),
now()
);
</insert>
<!-- 更新的statement
id:在该映射文件下的唯一标识。在使用动态代理之后,必须和接口的方法名一致。必须属性
parameterType:参数类型。可省略
-->
<update id="updateUser" >
UPDATE tb_user
SET
user_name = #{userName},
password = #{password},
name = #{name},
age = #{age},
sex = #{sex},
birthday = #{birthday},
updated = now()
WHERE
(id = #{id});
</update>
<!-- 删除的statement
id:在该映射文件下的唯一标识。在使用动态代理之后,必须和接口的方法名一致。必须属性
parameterType:参数类型。可省略
-->
<delete id="deleteUserById">
delete from tb_user where id=#{id}
</delete>
</mapper>
8.5.3.CommonSQL.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">
<!-- namespace(命名空间):映射文件的唯一标识 -->
<mapper namespace="CommonSQL">
<sql id="commonSql">
id,user_name,
password,
name,
age,
sex,
birthday,
created,
updated
</sql>
</mapper>
8.5.4.Mybatis-config.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>
<!-- 引入外部资源文件,resource:相对路径,url:绝对路径 -->
<properties resource="jdbc.properties" />
<settings>
<!-- 行为参数,name:参数名,value:参数值,默认为false,true:开启驼峰匹配,即从经典的数据库列名到经典的java属性名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<!-- 类型别名:type:类型的全路径,alias:别名名称,推荐和class类名一致 -->
<!-- <typeAlias type="cn.itcast.mybatis.pojo.User" alias="User"/> -->
<!-- 别名扫描,name:包的路径 -->
<package name="cn.itcast.mybatis.pojo"/>
</typeAliases>
<!-- 环境:说明可以配置多个,default:指定生效的环境 -->
<environments default="test">
<!-- id:环境的唯一标识 -->
<environment id="development">
<!-- 事务管理器,type:类型 -->
<transactionManager type="JDBC" />
<!-- 数据源:type-池类型的数据源 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
<environment id="test">
<!-- 事务管理器,type:类型 -->
<transactionManager type="JDBC" />
<!-- 数据源:type-池类型的数据源 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
<!-- 映射文件 -->
<mappers>
<mapper resource="CommonSQL.xml"/>
<mapper resource="UserMapper.xml" />
<mapper resource="UserDaoMapper.xml" />
<!--
1.映射文件和mapper接口在同一个目录下
2.命名一致
3.命名空间必须是接口的全路径
-->
<!-- <mapper class="cn.itcast.mybatis.mapper.UserMapper" /> -->
<!-- <package name="cn.itcast.mybatis.mapper"/> -->
</mappers>
</configuration>
8.5.5.UserMapperTest
public class UserMapperTest {
private UserMapper userMapper;
@Before
public void setUp() throws Exception {
String resource = "mybatis-config.xml";
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 构建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, "development");
// 获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(true);
this.userMapper = sqlSession.getMapper(UserMapper.class);
}
@Test
public void testQueryUsersLikeUserName(){
List<User> users = this.userMapper.queryUsersLikeUserName("zhang");
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testMap(){
Map<String,Object> map = new HashMap<>();
map.put("userName", "zhangsan");
map.put("password", "123456");
System.out.println(this.userMapper.loginByMap(map));
}
@Test
public void testLogin(){
System.out.println(this.userMapper.login("zhangsan", "123456"));
}
@Test
public void testQueryUsersByTableName(){
List<User> users = this.userMapper.queryUsersByTableName("tb_user");
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testQueryUserById() {
System.out.println(this.userMapper.queryUserById(1l));
}
@Test
public void testQueryUserAll() {
}
@Test
public void testInsertUser() {
User user = new User();
user.setAge(18);
user.setName("柳岩");
user.setPassword("123456");
user.setUserName("yanyan1");
user.setSex(3);
user.setBirthday(new Date());
this.userMapper.insertUser(user);
System.out.println(user.getId());
}
@Test
public void testUpdateUser() {
}
@Test
public void testDeleteUserById() {
}
}
9.动态sql
9.1.if
判断语句
案例:查询男性用户,如果输入了用户名,按用户名模糊查询
9.1.1.定义接口
在UserMapper接口中定义方法:
/**
* 查询男性用户,如果输入了用户名,按用户名模糊查询
* @param userName
* @return
*/
public List<User> queryUserListLikeUserName(@Param("userName")String userName);
9.1.2.编写mapper.xml
在UserMapper映射文件中,定义接口方法对应的Statement
<select id="queryUserListLikeUserName" resultType="User">
select * from tb_user where sex=1
<!-- if:判断
test:OGNL表达式
-->
<if test="userName!=null and userName.trim()!=''">
and user_name like '%' #{userName} '%'
</if>
</select>
9.1.3.测试
在UserMapperTest测试类中,添加测试用例
@Test
public void testQueryUserListLikeUserName(){
List<User> users = this.userMapper.queryUserListLikeUserName("zhang");
for (User user : users) {
System.out.println(user);
}
}
9.2.choose when otherwise
查询男性用户,如果输入了用户名则按照用户名模糊查找,否则如果输入了年龄则按照年龄查找,否则查找用户名为“zhangsan”的用户。
9.2.1.定义接口
在UserMapper接口中,定义接口方法:
/**
* 查询男性用户,如果输入了用户名则按照用户名模糊查找,否则如果输入了年龄则按照年龄查找,否则查找用户名为“zhangsan”的用户。
* @param userName
* @param age
* @return
*/
public List<User> queryUserListLikeUserNameOrAge(@Param("userName")String userName, @Param("age")Integer age);
9.2.2.编写mapper.xml
在UserMapper.xml中,定义接口方法对应的Statement
<select id="queryUserListLikeUserNameOrAge" resultType="User">
select * from tb_user where sex=1
<!-- choose:条件选择
when:test-判断条件,一旦有一个when成立,后续的when都不再执行
otherwise:所有的when都不成立时,才会执行
-->
<choose>
<when test="userName!=null and userName.trim()!=''">and user_name like '%' #{userName} '%'</when>
<when test="age != null">and age = #{age}</when>
<otherwise>and user_name = 'zhangsan' </otherwise>
</choose>
</select>
9.2.3.测试
在UserMapperTest测试类中,添加测试用例
@Test
public void testQueryUserListLikeUserNameOrAge(){
List<User> users = this.userMapper.queryUserListLikeUserNameOrAge(null, null);
for (User user : users) {
System.out.println(user);
}
}
9.3.where
案例:查询所有用户,如果输入了用户名按照用户名进行模糊查询,如果输入年龄,按照年龄进行查询,如果两者都输入,两个条件都要成立。
9.3.1.定义接口
在UserMapper接口中,定义接口方法:
/**
* 查询所有用户,如果输入了用户名按照用户名进行模糊查询,如果输入年龄,按照年龄进行查询,如果两者都输入,两个条件都要成立。
* @param userName
* @param age
* @return
*/
public List<User> queryUserListLikeUserNameAndAge(@Param("userName")String userName, @Param("age")Integer age);
9.3.2.编写mapper.xml
在UserMapper.xml中,定义接口方法对应的Statement
<select id="queryUserListLikeUserNameAndAge" resultType="User">
select * from tb_user
<!--
自动添加where关键字
有一定的纠错功能:去掉sql语句块之前多余的一个and|or
通常结合if或者choose使用
-->
<where>
<if test="userName!=null and userName.trim()!=''">user_name like '%' #{userName} '%'</if>
<if test="age!=null">and age = #{age}</if>
</where>
</select>
9.3.3.测试
在UserMapperTest测试类中,添加测试用例
@Test
public void testQueryUserListLikeUserNameAndAge(){
List<User> users = this.userMapper.queryUserListLikeUserNameAndAge(null, 30);
for (User user : users) {
System.out.println(user);
}
}
9.4.set
案例:修改用户信息,如果参数user中的某个属性为null,则不修改。
9.4.1.定义接口
在UserMapper接口中,定义接口方法:
/**
* 修改用户信息,如果参数user中的某个属性为null,则不修改。
* @param user
*/
public void updateUserSelective(User user);
9.4.2.编写mapper.xml
在UserMapper.xml中,定义接口方法对应的Statement
<update id="updateUserSelective" >
UPDATE tb_user
<!--
set自动添加set关键字
也有一定的纠错功能:自动去掉sql语句块之后多余的一个逗号
-->
<set>
<if test="userName!=null and userName.trim()!=''">user_name = #{userName},</if>
<if test="password!=null and password.trim()!=''">password = #{password},</if>
<if test="name!=null and name.trim()!=''">name = #{name},</if>
<if test="age!=null">age = #{age},</if>
<if test="sex!=null">sex = #{sex}</if>
</set>
WHERE
(id = #{id});
</update>
9.4.3.测试
在UserMapperTest测试类中,添加测试用例
@Test
public void testUpdateUserSelective(){
User user = new User();
user.setAge(18);
user.setName("柳岩");
user.setPassword("123456");
user.setUserName("yanyan2");
// user.setSex(3);
user.setBirthday(new Date());
user.setId(12l);
this.userMapper.updateUserSelective(user);
}
9.5.foreach
案例:按照多个id查询用户信息
在UserMapper接口中,定义接口方法:
/**
* 根据多个id查询用户信息
* @param ids
* @return
*/
public List<User> queryUserListByIds(@Param("ids")Long[] ids);
9.5.2.配置
在UserMapper.xml中,定义接口方法对应的Statement
<select id="queryUserListByIds" resultType="User">
select * from tb_user where id in
<!--
foreach:遍历集合
collection:接收的集合参数
item:遍历的集合中的一个元素
separator:分隔符
open:以什么开始
close:以什么结束
-->
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
9.5.3.测试
在UserMapperTest测试类中,添加测试用例
@Test
public void testQueryUserListByIds(){
List<User> users = this.userMapper.queryUserListByIds(new Long[]{1l,2l,3l,4l});
for (User user : users) {
System.out.println(user);
}
}
10.缓存
执行相同的sql语句和参数,mybatis不进行执行sql,而是从缓存中命中返回。
10.1.一级缓存
在mybatis中,一级缓存默认是开启的,并且一直无法关闭,作用域:在同一个sqlSession下。
在UserMapperTest中添加测试一级缓存的方法:
@Test
public void testCache(){
User user1 = this.userMapper.queryUserById(1l);
System.out.println(user1);
System.out.println("=================第二次查询======================");
User user2 = this.userMapper.queryUserById(1l);
System.out.println(user2);
}
由于一级缓存的存在,此时在log日志中,应该只会在第一次查询是执行sql语句,第二次查询时直接从缓存中命中,即不再执行sql语句。
使用:sqlSession.clearCache();可以强制清除缓存
在测试方法中清空一级缓存:
@Test
public void testCache(){
User user1 = this.userMapper.queryUserById(1l);
System.out.println(user1);
this.sqlSession.clearCache();
System.out.println("=================第二次查询======================");
User user2 = this.userMapper.queryUserById(1l);
System.out.println(user2);
}
在执行第二次查询之前清空缓存,再去执行查询。这时无法从缓存中命中,便会去执行sql从数据库中查询。
执行update、insert、delete的时候,会清空缓存
在测试方法中添加更新的操作:
@Test
public void testCache(){
User user1 = this.userMapper.queryUserById(1l);
System.out.println(user1);
// this.sqlSession.clearCache();
System.out.println("================更新======================");
User user = new User();
user.setAge(18);
user.setName("柳岩");
user.setPassword("123456");
user.setUserName("yanyan2");
// user.setSex(3);
user.setBirthday(new Date());
user.setId(12l);
this.userMapper.updateUser (user);
System.out.println("=================第二次查询======================");
User user2 = this.userMapper.queryUserById(1l);
System.out.println(user2);
}
由于insert、update、delete会清空缓存,所以第二次查询时,依然会输出sql语句,即从数据库中查询。
10.2.二级缓存
mybatis 的二级缓存的作用域:
1、同一个mapper的namespace,同一个namespace中查询sql可以从缓存中命中。
2、跨sqlSession,不同的SqlSession可以从二级缓存中命中
怎么开启二级缓存:
1、在映射文件中,添加标签
2、在全局配置文件中,设置cacheEnabled参数,默认已开启。
注意:
由于缓存数据是在sqlSession调用close方法时,放入二级缓存的,所以第一个sqlSession必须先关闭。
二级缓存的对象必须序列化,例如:User对象必须实现Serializable接口。
开启二级缓存,在映射文件(UserMapper.xml)中添加:
在UserMapperTest中添加二级缓存的测试方法:
给User对象实现序列化接口后,重新运行测试用例,日志:
执行update、insert、delete的时候,会清空缓存
@Test
public void testCache2(){
User user1 = this.userMapper.queryUserById(1l);
System.out.println(user1);
// 注意:关闭sqlSession
sqlSession.close();
System.out.println("=======================================");
// 重新打开一个sqlSession会话
SqlSession sqlSession2 = this.sqlSessionFactory.openSession();
// 通过sqlSession2重新实例化UserMapper
this.userMapper = sqlSession2.getMapper(UserMapper.class);
User user = new User();
user.setAge(18);
user.setName("柳岩");
user.setPassword("123456");
user.setUserName("yanyan2");
user.setBirthday(new Date());
user.setId(12l);
this.userMapper.updateUserSelective(user);
System.out.println("=======================================");
User user2 = this.userMapper.queryUserById(1l);
System.out.println(user2);
}
关闭二级缓存:
不开启,或者在全局的mybatis-config.xml 中去关闭二级缓存
在mybatis-config.xml配置中:
<settings>
<!-- 行为参数,name:参数名,value:参数值,默认为false,true:开启驼峰匹配,即从经典的数据库列名到经典的java属性名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 关闭二级缓存,默认是开启,false:关闭 -->
<setting name="cacheEnabled" value="false"/>
</settings>
11.高级查询(OrderMapper.xml)
11.1.表关系说明
需求说明:
目录结构:
11.2.一对一查询
需求:查询出订单信息,并查询出下单人信息
11.2.1.扩展Order类的实现方式(了解)
思路:
创建新的pojo对象OrderUser继承Order对象,这样OrderUser就从Order中继承了订单信息。
再把User对象中的属性copy到OrderUser对象中,这样OrderUser又有了User中的用户信息。
把sql语句的结果集封装成OrderUser对象,那么结果中就既包含了订单信息又包含了下单人信息。
新建OrderUser实体类继承Order,在把User中的属性copy到OrderUser中,并添加get、set方法:
在OrderMapper接口中,添加查询方法:
在OrderMapper.xml中,配置对应的Statement,并把结果集封装成OrderUser对象:
在OrderMapperTest中添加测试方法:
11.2.2.添加User属性的实现方式
思路:
从订单的角度来看,订单和用户的关系是一对一的关系,并且通过tb_order表中的id进行关联。
用面向对象的思想实现,就是在Order对象中添加user属性。
在Order对象中添加User属性,并添加user的get、set方法:
在OrderMapper接口中,添加接口方法:
/**
* 根据订单号查询订单信息,并且查询出下单人信息
* @param number
* @return
*/
public Order queryOrderWithUser(@Param("number")String number);
使用resultType不能完成user对象的自动映射,需要手动完成结果集映射,即使用resultMap标签自定义映射。
在OrderMapper.xml中配置,结果集的映射,这时必须使用resultMap:
<resultMap type="Order" id="orderUserMap" autoMapping="true">
<id column="id" property="id"/>
<!--
association:一对一的映射
property:java的属性名
javaType:属性名对应的java类型
autoMapping:开启自动映射
子标签:参照resultMap
-->
<association property="user" javaType="User" autoMapping="true">
<id column="user_id" property="id"/>
</association>
</resultMap>
<!-- resultType不能完成user信息的映射,必须使用resultMap,resultMap的值对应resultMap标签的id,resultMap和resultType必须二选一 -->
<select id="queryOrderWithUser" resultMap="orderUserMap">
select * from tb_order a
LEFT JOIN tb_user b on a.user_id=b.id
where a.order_number = #{number}
</select>
在OrderMapperTest添加测试:
@Test
public void testQueryOrderWithUser() {
System.out.println(this.orderMapper.queryOrderWithUser("20140921001"));
}
11.3.一对多查询
一对多查询:查询订单,查询出下单人信息并且查询出订单详情。
思路:
订单 : 订单详情 = 1 : n(体现在pojo对象中,就是在Order对象中添加OrderDetail对象的集合)
SQL:
在Order类添加List属性,并添加get、set方法:
OrderMapper接口:
/**
* 查询订单,查询出下单人信息并且查询出订单详情。
* @param number
* @return
*/
public Order queryOrderWithUserDetail(@Param("number")String number);
OrderMapper映射:
<resultMap type="Order" id="orderUserDetailMap" autoMapping="true">
<id column="id" property="id"/>
<association property="user" javaType="User" autoMapping="true">
<id column="user_id" property="id"/>
</association>
<!--
collection:一对多的查询
property:属性名
javaType:集合类型
ofType:集合中的元素类型
autoMapping:开启自动映射
子标签:参照resultMap
-->
<collection property="detailList" javaType="list" ofType="Orderdetail" autoMapping="true">
<id column="detail_id" property="id"/>
</collection>
</resultMap>
<select id="queryOrderWithUserDetail" resultMap="orderUserDetailMap">
select *,c.id as detail_id from tb_order a
LEFT JOIN tb_user b on a.user_id=b.id
LEFT JOIN tb_orderdetail c on a.id=c.order_id
where a.order_number=#{number}
</select>
OrderMapperTest测试:
@Test
public void testQueryOrderWithUserDetail(){
System.out.println(this.orderMapper.queryOrderWithUserDetail("20140921001"));
}
11.4.多对多查询
多对多查询:查询订单,查询出下单人信息并且查询出订单详情中的商品数据。
思路:
订单:订单详情 = 1 : n(体现在pojo对象中就是在Order对象中添加OrderDetail对象的集合)
订单详情:商品 = 1 : 1(体现在pojo对象中就是在OrderDetail对象中添加Item对象)
Sql:
在Orderdetail添加Item属性,并添加get、set方法:
OrderMapper接口:
/**
* 查询订单,查询出下单人信息并且查询出订单详情中的商品数据。
* @param number
* @return
*/
public Order queryOrderWithUserDetailItem(@Param("number")String number);
OrderMapper配置(通过在collection标签中嵌套使用association标签):
<resultMap type="Order" id="orderUserDetailItemMap" autoMapping="true">
<id column="id" property="id"/>
<association property="user" javaType="User" autoMapping="true">
<id column="user_id" property="id"/>
</association>
<collection property="detailList" javaType="list" ofType="Orderdetail" autoMapping="true">
<id column="detail_id" property="id"></id>
<association property="item" javaType="Item" autoMapping="true">
<id column="item_id" property="id"/>
</association>
</collection>
</resultMap>
<select id="queryOrderWithUserDetailItem" resultMap="orderUserDetailItemMap">
select *,c.id as detail_id from tb_order a
LEFT JOIN tb_user b on a.user_id=b.id
LEFT JOIN tb_orderdetail c on a.id=c.order_id
LEFT JOIN tb_item d on c.item_id=d.id
where a.order_number=#{number}
</select>
OrderMapperTest测试:
@Test
public void testQueryOrderWithUserDetailItem(){
System.out.println(this.orderMapper.queryOrderWithUserDetailItem("20140921001"));
}
11.5.resultMap的继承
11.6.高级查询的整理
resutlType无法帮助我们自动的去完成映射,所以只有使用resultMap手动的进行映射
resultMap:
- type 结果集对应的数据类型
- id 唯一标识,被引用的时候,进行指定
- autoMapping 开启自动映射
- extends 继承
子标签:
association:一对一的映射
-
property 定义对象的属性名
-
javaType 属性的类型
-
autoMapping 开启自动映射
collection:一对多的映射
-
property 定义对象的属性名
-
javaType 集合的类型
-
ofType 集合中的元素类型
-
autoMapping 开启自动映射
12.延迟加载
分析:
采用之前的配置方式(参考高级查询),肯定是不能做到延迟加载的,因为咱是通过一个查询sql直接查询出所有的数据。为了测试延迟加载的效果,必须改造高级查询的配置,使Order的查询和User或者OrderDetail的查询分开。只有当我们访问Order对象的User或者OrderDetail属性时,才去执行User或者OrderDetail的查询。
12.1.改造一对一查询
在OrderMapper接口中,编写接口方法:
/**
* 测试延迟加载
* @param number
* @return
*/
public Order queryOrderLazy(@Param("number")String number);
OrderMapper配置:
处理组合键时,需要传递多个参数,可以使用column=”{prop1=col1, prop2=col2, prop3=col3…}”,设置多个列名传入到嵌套查询语句,mybatis会把prop1,prop2,prop3设置到目标嵌套的查询语句中的参数对象中。
子查询中,必须通过prop1,prop2,prop3获取对应的参数值,你也可以使用这种方式指定参数名例如:
测试:
日志信息:
分析:红色部分为查询Order信息的日志,绿色为查询Order中的User信息的日志。已经成功拆分出两个子查询,并且查询出了带有User信息的Order信息,说明改造OK。
接下来开启延迟加载
12.2.开启延迟加载
在mybatis-config.xml中配置行为参数:
修改测试用例,注释掉打印Order信息的这行代码:
执行,报错:
说明延迟加载需要cglib的支持。
在pom.xml中,添加cglib的依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
执行:
13.如果sql语句中出现’<’的解决方案
1、使用xml中的字符实体
2、使用<![CDATA[ < ]]>