Mybatis
7种 3种 jdbc
1.JDBC回顾
1.1.创建mybatis库,执行sql
SQL文件:
执行后:
tb_user表:
1.2.创建maven工程
右键—>new–>Project
选择:Maven—>Maven Project
坐标:
1.3.引入mysql依赖包
Pom.xml内容:
4.0.0
cn.itcast.parent
itcast-parent
0.0.1-SNAPSHOT
cn.itcast.mybatis
mybatis
1.0.0-SNAPSHOT
mysql
mysql-connector-java
1.4.需求
根据id查询用户信息
1.5.JDBCTest
package cn.itcast.mybatis.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class JDBCTest {
public static void main(String[] args) throws Exception {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
String url = "jdbc:mysql://127.0.0.1:3306/mybatis-44";
String user = "root";
String password = "root";
conn = DriverManager.getConnection(url, user, password);
// 获取statement,preparedStatement
String sql = "select * from tb_user where id=?";
ps = conn.prepareStatement(sql);
// 设置参数
ps.setLong(1, 1l);
// 执行查询,获取结果集
rs = ps.executeQuery();
// 处理结果集
while(rs.next()){
System.out.println(rs.getString("user_name"));
System.out.println(rs.getString("name"));
System.out.println(rs.getInt("age"));
}
} finally {
// 关闭连接,释放资源
if(rs!=null){
rs.close();
}
if(ps!=null){
ps.close();
}
if(conn!=null){
conn.close();
}
}
}
}
1.6.缺点及解决方案
2.MyBatis介绍
2.1.简介
2.2.官方文档
2.3.特点
Mybatis:
1)支持自定义SQL、存储过程、及高级映射
2)实现自动对SQL的参数设置
3)实现自动对结果集进行解析和封装
4)通过XML或者注解进行配置和映射,大大减少代码量
5)数据源的连接信息通过配置文件进行配置
可以发现,MyBatis是对JDBC进行了简单的封装,帮助用户进行SQL参数的自动设置,以及结果集与Java对象的自动映射。与Hibernate相比,配置更加简单、灵活、执行效率高。但是正因为此,所以没有实现完全自动化,需要手写SQL,这是优点也是缺点。
因此,对性能要求较高的电商类项目,一般会使用MyBatis,而对与业务逻辑复杂,不太在乎执行效率的传统行业,一般会使用Hibernate
Ssm:struts spring mybatis
Ssh:springmvc spring 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.快速入门(Mybatis第一个程序)
3.1.目录结构
pojo中的User.java参照课前资料:
3.2.引入依赖(pom.xml)
参考itcast-parent工程
org.mybatis
mybatis
3.3.全局配置文件(mybatis-config.xml)
Mybatis-config.xml内容,参照官方文档:
<?xml version="1.0" encoding="UTF-8" ?> 3.4.映射文件(UserMapper.xml)UserMapper.xml映射文件内容,参照官方文档:
<?xml version="1.0" encoding="UTF-8" ?><!-- 查询的statement,id:在同一个命名空间下的唯一标识,resultType:sql语句的结果集封装类型 -->
<select id="queryUserById" resultType="User">
select * from tb_user where id=#{id}
</select>
3.5.编写mybatis程序(MybatisTest.java)
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();
}
}
}
3.6.测试运行报错
原因:
3.7.其他典型出错
原因:
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接口
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
实现UserDao接口:
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" ?><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>
5.4.引入UserDaoMapper.xml
在mybatis-config.xml中引入UserDaoMapper.xml映射文件:
5.5.测试UserDao
针对UserDao的测试,咱们使用Junit进行测试。
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.总结
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接口的测试文件,即可以少写一个接口的实现类
1.2.3.4.5.6.6.1.6.2.6.2.1.创建UserMapper接口
添加后:
UserMapper.java的内容参考UserDao.java的内容。(可以从UserDao直接Copy过来)
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映射文件,之前咱们已创建,但是内容参考UserDaoMapper。
名称空间必须改成UserMapper接口的全路径,StatementId必须和接口方法名一致,结果集的封装类型已经和方法的返回类型一致
<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>
6.2.3.UserMapperTest测试
添加UserMapper.java的测试用例。只需要修改一句代码即可
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资源文件:
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资源文件:
<?xml version="1.0" encoding="UTF-8" ?> 通过properties引入外部资源文件之后,就可以通过${xxx}的方式使用资源文件里的参数了。7.2.settings设置
settings参数有很多,咱们只需要学习以下4个参数就行了,今天咱们先学习驼峰匹配。
设置参数 描述 有效值 默认值
cacheEnabled 该配置影响的所有映射器中配置的缓存的全局开关。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 true | false false
aggressiveLazyLoading 当启用时,带有延迟加载属性的对象的加载与否完全取决于对任意延迟属性的调用;反之,每种属性将会按需加载。 true | false true
mapUnderscoreToCamelCase 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 true | false False
开启驼峰匹配:完成经典的数据库命名到java属性的映射
经典数据库命名:如果多个单词之间,通常使用下划线进行连接。
java中命名:第二个单词首字母大写。
驼峰匹配:相当于去掉数据中的名字的下划线,和java进行匹配
查询数据的时候,查不到userName的信息,原因:数据库的字段名是user_name,POJO中的属性名字是userName,两端不一致,造成mybatis无法填充对应的字段信息。修改方法:在sql语句中使用别名
解决方案1:在sql语句中使用别名
解决方案2:参考驼峰匹配 — mybatis-config.xml
mybatis-config.xml中开启驼峰匹配:
<?xml version="1.0" encoding="UTF-8" ?>在UserMapper.xml中修改sql语句删除别名
测试日志截图,如下:
删除sql中的别名,开启驼峰匹配后,仍然能够获取用户名,说明驼峰匹配起到了作用
7.3.typeAliases
之前咱们在映射文件中用到java类型时,都是使用类的全路径,书写起来非常麻烦
解决方案:
类型别名是为 Java 类型命名的一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。(官方文档)
7.3.1.方式一:typeAlias
缺点:每个pojo类都要去配置。
7.3.2.方式二:package
扫描指定包下的所有类,扫描之后的别名就是类名,大小写不敏感(不区分大小写),建议使用的时候和类名一致。
在映射文件中使用类型别名:
已经为普通的 Java 类型内建了许多相应的类型别名。它们都是大小写不敏感的,需要注意的是由于重载原始类型的名称所做的特殊处理。
别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator
7.4.typeHandlers(类型处理器)
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。
类型处理器 Java 类型 JDBC 类型
BooleanTypeHandler java.lang.Boolean, boolean 任何兼容的布尔值
ByteTypeHandler java.lang.Byte, byte 任何兼容的数字或字节类型
ShortTypeHandler java.lang.Short, short 任何兼容的数字或短整型
IntegerTypeHandler java.lang.Integer, int 任何兼容的数字和整型
LongTypeHandler java.lang.Long, long 任何兼容的数字或长整型
FloatTypeHandler java.lang.Float, float 任何兼容的数字或单精度浮点型
DoubleTypeHandler java.lang.Double, double 任何兼容的数字或双精度浮点型
BigDecimalTypeHandler java.math.BigDecimal 任何兼容的数字或十进制小数类型
StringTypeHandler java.lang.String CHAR 和 VARCHAR 类型
ClobTypeHandler java.lang.String CLOB 和 LONGVARCHAR 类型
NStringTypeHandler java.lang.String NVARCHAR 和 NCHAR 类型
NClobTypeHandler java.lang.String NCLOB 类型
ByteArrayTypeHandler byte[] 任何兼容的字节流类型
BlobTypeHandler byte[] BLOB 和 LONGVARBINARY 类型
DateTypeHandler java.util.Date TIMESTAMP 类型
DateOnlyTypeHandler java.util.Date DATE 类型
TimeOnlyTypeHandler java.util.Date TIME 类型
SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP 类型
SqlDateTypeHandler java.sql.Date DATE 类型
SqlTimeTypeHandler java.sql.Time TIME 类型
ObjectTypeHandler Any 其他或未指定类型
EnumTypeHandler Enumeration Type VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引)
EnumOrdinalTypeHandler Enumeration Type 任何兼容的 NUMERIC 或 DOUBLE 类型,作为位置存储(而不是代码本身)。
7.5.plugins(插件又称拦截器)
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
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引入项目目录下的映射文件:
缺点:每次都要在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个方法来完成操作。
在UserMapper接口中,添加根据表名查询用户信息的方法:
在UserMapper映射文件中,添加方法对应的statement:
输出(报错):
如果你要动态传入的字段名是表名,并且sql执行是预编译的,这显然是不行的,所以你必须改成非预编译的,也就是这样:
注意:
使用
去
接
收
参
数
信
息
,
在
一
个
参
数
时
,
默
认
情
况
下
必
须
使
用
{} 去接收参数信息,在一个参数时,默认情况下必须使用
去接收参数信息,在一个参数时,默认情况下必须使用{value}获取参数值,
而#{} 只是表示占位,与参数的名字无关,如果只有一个参数,可以使用任意参数名接收参数值,会自动对应。
但是这并不是一种稳妥的解决方案,推荐使用@Param注解指定参数名,所以以上接口及映射文件可以改成如下:
一个参数时,在使用#{}传参时,可以通过任意参数名接收参数;而${},默认必须通过value来接收参数。
可以通过@Param注解指定参数名
8.2.2.多个参数
当mapper接口要传递多个参数时,有两种传递参数的方法:
1、默认规则获取参数{0,1,param1,param2}
2、使用@Param注解指定参数名
案例:实现一个简单的用户登录,根据username和password验证用户信息
在UserMapper接口中,添加登陆方法:
在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包装类
前面已经使用了基本数据类型和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标签来使用,如改造根据表名查询用户信息:
测试:
测试结果:
2017-07-26 10:18:08,622 [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
2017-07-26 10:18:08,622 [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
2017-07-26 10:18:08,622 [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
2017-07-26 10:18:08,854 [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
2017-07-26 10:18:09,130 [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 802102645.
2017-07-26 10:18:09,132 [main] [cn.itcast.mybatis.mapper.UserMapper.queryUsersLikeUserName]-[DEBUG] ==> Preparing: select id,user_name, password, name, age, sex, birthday, created, updated from tb_user where user_name like ‘%’ ? ‘%’
2017-07-26 10:18:09,185 [main] [cn.itcast.mybatis.mapper.UserMapper.queryUsersLikeUserName]-[DEBUG] > Parameters: zhang(String)
2017-07-26 10:18:09,213 [main] [cn.itcast.mybatis.mapper.UserMapper.queryUsersLikeUserName]-[DEBUG] < Total: 2
User [id=1, userName=zhangsan, password=123456, name=张三, age=30, sex=1, birthday=Wed Aug 08 00:00:00 CST 1984, created=Fri Sep 19 16:56:04 CST 2014, updated=Sun Sep 21 11:24:59 CST 2014]
User [id=4, userName=zhangwei, password=123456, name=张伟, age=20, sex=1, birthday=Thu Sep 01 00:00:00 CDT 1988, created=Fri Sep 19 16:56:04 CST 2014, updated=Fri Sep 19 16:56:04 CST 2014]
8.4.2.用法二
很多时候同一个sql片段,可能在很多映射文件里都有使用,这就需要添加一个映射文件,用来统一定义sql片段。
如下,在resource目录下新增CommonSQL.xml文件:
内容如下:
<?xml version="1.0" encoding="UTF-8" ?><sql id="commonSql">
id,user_name,
password,
name,
age,
sex,
birthday,
created,
updated
</sql>
定义好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" ?><!-- <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>
8.5.3.CommonSQL.xml
<?xml version="1.0" encoding="UTF-8" ?><sql id="commonSql">
id,user_name,
password,
name,
age,
sex,
birthday,
created,
updated
</sql>
8.5.4.Mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>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 queryUserListLikeUserName(@Param(“userName”)String userName);
9.1.2.编写mapper.xml
在UserMapper映射文件中,定义接口方法对应的Statement
select * from tb_user where sex=1
and user_name like ‘%’ #{userName} ‘%’
9.1.3.测试
在UserMapperTest测试类中,添加测试用例
@Test
public void testQueryUserListLikeUserName(){
List 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 queryUserListLikeUserNameOrAge(@Param(“userName”)String userName, @Param(“age”)Integer age);
9.2.2.编写mapper.xml
在UserMapper.xml中,定义接口方法对应的Statement
select * from tb_user where sex=1
and user_name like ‘%’ #{userName} ‘%’
and age = #{age}
and user_name = ‘zhangsan’
9.2.3.测试
在UserMapperTest测试类中,添加测试用例
@Test
public void testQueryUserListLikeUserNameOrAge(){
List 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 queryUserListLikeUserNameAndAge(@Param(“userName”)String userName, @Param(“age”)Integer age);
9.3.2.编写mapper.xml
在UserMapper.xml中,定义接口方法对应的Statement
select * from tb_user
user_name like ‘%’ #{userName} ‘%’
and age = #{age}
9.3.3.测试
在UserMapperTest测试类中,添加测试用例
@Test
public void testQueryUserListLikeUserNameAndAge(){
List 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 tb_user
user_name = #{userName},
password = #{password},
name = #{name},
age = #{age},
sex = #{sex}
WHERE
(id = #{id});
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查询用户信息
9.5.1.接口
在UserMapper接口中,定义接口方法:
/**
* 根据多个id查询用户信息
* @param ids
* @return
*/
public List queryUserListByIds(@Param(“ids”)Long[] ids);
9.5.2.配置
在UserMapper.xml中,定义接口方法对应的Statement
select * from tb_user where id in
#{id}
9.5.3.测试
在UserMapperTest测试类中,添加测试用例
@Test
public void testQueryUserListByIds(){
List users = this.userMapper.queryUserListByIds(new Long[]{1l,2l,3l,4l});
for (User user : users) {
System.out.println(user);
}
}
9.6.本章代码汇总
9.6.1.UserMapper.xml
select * from tb_user where sex < 2
and user_name like ‘%’ #{userName} ‘%’
<select id="queryUserListLikeUserNameOrAge" resultType="User">
select * from tb_user where sex=1
<!-- choose:条件选择
when:test-条件,使用方式参考if的test属性,一旦有一个成立,后续的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>
<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>
<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>
<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.6.2.UserMapper.java
/**
* 查询男性用户,如果输入了用户名,按用户名模糊查询
* @param userName
* @return
*/
public List queryUserListLikeUserName(@Param(“userName”)String userName);
/**
* 查询男性用户,如果输入了姓名则按照姓名模糊查找,否则如果输入了年龄则按照年龄查找,否则查找姓名为“张三”的用户。
* @param userName
* @param age
* @return
*/
public List<User> queryUserListLikeUserNameOrAge(@Param("userName")String userName, @Param("age")Integer age);
/**
* 查询所有用户,如果输入了姓名按照姓名进行模糊查询,如果输入年龄,按照年龄进行查询,如果两者都输入,两个条件都要成立。
* @param userName
* @param age
* @return
*/
public List<User> queryUserListLikeUserNameAndAge(@Param("userName")String userName, @Param("age")Integer age);
/**
* 修改用户信息,如果参数user中的某个属性为null,则不修改。
* @param user
*/
public void updateUserSelective(User user);
/**
* 根据多个id查询用户信息
* @param ids
* @return
*/
public List<User> queryUserListByIds(@Param("ids")Long[] ids);
9.6.3.UserMapperTest.java
@Test
public void testQueryUserListByIds(){
List users = this.userMapper.queryUserListByIds(new Long[]{1l,2l,3l,4l});
for (User user : users) {
System.out.println(user);
}
}
@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);
}
@Test
public void testQueryUserListLikeUserNameAndAge(){
List<User> users = this.userMapper.queryUserListLikeUserNameAndAge(null, 30);
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testQueryUserListLikeUserNameOrAge(){
List<User> users = this.userMapper.queryUserListLikeUserNameOrAge(null, null);
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testQueryUserListLikeUserName(){
List<User> users = this.userMapper.queryUserListLikeUserName("zhang");
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中添加二级缓存的测试方法:
@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 user2 = this.userMapper.queryUserById(1l);
System.out.println(user2);
}
运行测试用例:
说明二级缓存,必须序列化,使User类实现序列化接口:
给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配置中:
11.高级查询(OrderMapper.xml)
11.1.表关系说明
需求说明:
目录结构:
创建OrderMapper.java接口,参考UserMapper.java。
添加OrderMapper.xml映射文件,参考UserMapper.xml。
创建OrderMapperTest测试用例,参考UserMapperTest。
Pojo参考课前资料:
11.2.一对一查询
需求:查询出订单信息,并查询出下单人信息
Sql:
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:
<!-- 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添加测试:
public class OrderMapperTest {
private OrderMapper orderMapper;
@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.orderMapper = sqlSession.getMapper(OrderMapper.class);
}
@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映射:
<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标签):
<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.延迟加载
设置参数 描述 有效值 默认值
cacheEnabled 该配置影响的所有映射器中配置的缓存的全局开关。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 true | false false
aggressiveLazyLoading 当启用时,带有延迟加载属性的对象的加载与否完全取决于对任意延迟属性的调用;反之,每种属性将会按需加载。 true | false true
mapUnderscoreToCamelCase 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 true | false False
分析:
采用之前的配置方式(参考高级查询),肯定是不能做到延迟加载的,因为咱是通过一个查询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获取对应的参数值,你也可以使用这种方式指定参数名例如:
测试:
日志信息:
2017-04-13 00:11:14,982 [main] [cn.itcast.mybatis.mapper.OrderMapper.queryOrderLazy]-[DEBUG] ==> Preparing: select * from tb_order where order_number=?
2017-04-13 00:11:15,028 [main] [cn.itcast.mybatis.mapper.OrderMapper.queryOrderLazy]-[DEBUG] ==> Parameters: 20140921001(String)
2017-04-13 00:11:15,049 [main] [cn.itcast.mybatis.mapper.OrderMapper.queryUser]-[DEBUG] ==> Preparing: select * from tb_user where id=?
2017-04-13 00:11:15,049 [main] [cn.itcast.mybatis.mapper.OrderMapper.queryUser]-[DEBUG] > Parameters: 1(Long)
2017-04-13 00:11:15,057 [main] [cn.itcast.mybatis.mapper.OrderMapper.queryUser]-[DEBUG] < Total: 1
2017-04-13 00:11:15,057 [main] [cn.itcast.mybatis.mapper.OrderMapper.queryOrderLazy]-[DEBUG] < Total: 1
Order [id=1, userId=null, orderNumber=20140921001, user=User [id=1, userName=zhangsan, password=123456, name=张三, age=30, sex=1, birthday=Wed Aug 08 00:00:00 CST 1984, created=Fri Sep 19 16:56:04 CST 2014, updated=Sun Sep 21 11:24:59 CST 2014], detailList=null]
20140921001
User [id=1, userName=zhangsan, password=123456, name=张三, age=30, sex=1, birthday=Wed Aug 08 00:00:00 CST 1984, created=Fri Sep 19 16:56:04 CST 2014, updated=Sun Sep 21 11:24:59 CST 2014]
分析:红色部分为查询Order信息的日志,绿色为查询Order中的User信息的日志。已经成功拆分出两个子查询,并且查询出了带有User信息的Order信息,说明改造OK。
接下来开启延迟加载
12.2.开启延迟加载
在mybatis-config.xml中配置行为参数:
修改测试用例,注释掉打印Order信息的这行代码:
执行,报错:
说明延迟加载需要cglib的支持。
在pom.xml中,添加cglib的依赖:
cglib
cglib
3.1
执行:
13.如果sql语句中出现’<’的解决方案
1、使用xml中的字符实体
2、使用<![CDATA[ < ]]>