文章目录
1 JDBC
1.1 创建mysql数据库
执行目录DB的脚本mytatis_demo.sql。
1.2 idea创建maven工程
略
1.3 添加依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
1.4 需求
根据id查询用户
1.5 JDBCTest
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/mybatisdemo";
String user = "root";
String password = "258369";
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 特点
- 支持自定义SQL、存储过程、及高级映射
- 实现自动对SQL的参数设置
- 实现自动对结果集进行解析和封装
- 通过XML或者注解进行配置和映射,大大减少代码量
- 数据源的连接信息通过配置文件进行配置
MyBatis是对JDBC进行了简单的封装,帮助用户进行SQL参数的自动设置,以及结果集与Java对象的自动映射。与Hibernate相比,配置更加简单、灵活、执行效率高。但是正因为此,所以没有实现完全自动化,需要手写SQL,这是优点也是缺点。
2.2 整体架构
- 配置文件
全局配置文件:mybatis-config.xml -> hibernate.cfg.xml,作用:配置数据源,引入映射文件映射文件:XxMapper.xml -> xx.hbm.xml,作用:配置sql语句、参数、结果集封装类型等 - SqlSessionFactory
相当于Hibernate的SessionFactory,作用:获取SqlSession
通过newSqlSessionFactoryBuilder().build(inputStream)来构建,inputStream:读取配置文件的IO流 - SqlSession
相当于Hibernate的Session,作用:执行CRUD操作 - Executor
执行器,SqlSession通过调用它来完成具体的CRUD
它是一个接口,提供了两种实现:缓存的实现、数据库的实现 - Mapped Statement
在映射文件里面配置,包含3部分内容:
具体的sql,sql执行所需的参数类型,sql执行结果的封装类型
参数类型和结果集封装类型包括3种:
HashMap,基本数据类型,pojo
2.3 快速入门
2.3.1 添加依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
2.3.2 全局配置文件
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>
2.3.3 映射文件
<?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="com.freedom.mybatis.User">
select * from tb_user where id=#{id}
</select>
</mapper>
2.3.4 编写代码
public class MybatisTest {
public static void main(String[] args) throws IOException {
SqlSession sqlSession = null;
try {
// 指定配置文件
String resource = "mybatis-config.xml";
// 读取配置文件
InputStream is = Resources.getResourceAsStream(resource);
// 构建SqlSessionFactory
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(is);
// 获得sqlSession
sqlSession = build.openSession();
// 执行查询操作,获取结果集。参数:1-命名空间(namespace)+“.”+statementId,2-sql的占位符参数
User user = sqlSession.selectOne("UserMapper.queryUserById", 1L);
System.out.println(user);
} finally {
// 关闭连接
if (sqlSession != null) {
sqlSession.close();
}
}
}
}
2.4 引入log日志
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version>
</dependency>
log4j.rootLogger=DEBUG,A1
log4j.logger.org.mybatis=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
2.5 使用总结
- 配置mybatis-config.xml 全局的配置文件 (1、数据源,2、外部的mapper)
- 创建SqlSessionFactory
- 通过SqlSessionFactory创建SqlSession对象
- 通过SqlSession操作数据库 CRUD
- 调用session.commit()提交事务
- 调用session.close()关闭会话
3 完整CRUD操作
3.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);
}
3.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();
}
}
3.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>
3.4 引入UserDaoMapper.xml
在mybatis-config.xml中引入UserDaoMapper.xml映射文件:
3.5 测试UseDao
添加依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
3.6 编写测试代码
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 queryUserById() {
User user = this.userDao.queryUserById(1l);
System.out.println(user);
}
@Test
public void queryUserAll() {
}
@Test
public void insertUser() {
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 updateUser() {
// 查询
User user = this.userDao.queryUserById(7l);
// 更新
user.setAge(28);
user.setPassword("111111");
this.userDao.updateUser(user);
}
@Test
public void deleteUserById() {
this.userDao.deleteUserById(7l);
}
}
4 动态代理Mapper实现类
4.1 思考问题
- 接口->实现类->mapper.xml。
- 实现类中,使用mybatis的方式非常类似。
- sql statement 硬编码到java代码中。
思考:能否只写接口,不书写实现类,只编写Mapper.xml即可
因为在dao(mapper)的实现类中对sqlsession的使用方式很类似。mybatis提供了接口的动态代理。
Mapper接口的动态代理实现,需要满足以下条件:
- 映射文件中的命名空间与Mapper接口的全路径一致
- 映射文件中的statementId与Mapper接口的方法名保持一致
- 映射文件中的statement的ResultType必须和mapper接口方法的返回类型一致(即使不采用动态代理,也要一致)
- 映射文件中的statement的parameterType必须和mapper接口方法的参数类型一致(不一定,该参数可省略)
4.2 使用动态代理改造CRUD
采用动态代理之后,只剩下UserMapper接口、UserMapper.xml映射文件以及UserMapper接口的测试文件,即可以少写一个接口的实现类。
4.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);
}
4.2.2 UserMapper映射文件
之前创建,在基础上改造
<?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="com.freedom.mapper.UserMapper">
<select id="queryUserById" resultType="com.freedom.mybatis.User">
select * from tb_user where id = #{id}
</select>
<select id="queryUserAll" resultType="com.freedom.mybatis.User">
select * from tb_user
</select>
<insert id="insertUser" parameterType="com.freedom.mybatis.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="com.freedom.mybatis.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>
4.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 queryUserAll() {
List<User> userList = this.userMapper.queryUserAll();
for (User user : userList) {
System.out.println(user);
}
}
}
5 mybatis-config.xml配置
mybatis-config.xml讲究严格的顺序,具体顺序遵循文档的顺序:
5.1 properties属性读取外部资源
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mybatisdemo
username=root
password=258369
5.2 settings设置
学四个参数
user_name查询不到,解决方案:
- 用别名 select user_name as userName from tb_user.
- 开启驼峰匹配
重新查询
5.3 typeAliases
之前咱们在映射文件中用到java类型时,都是使用类的全路径,书写起来非常麻烦
解决方案:
类型别名是为 Java 类型命名的一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。(官方文档)
5.3.1 方式一:typeAlias
5.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 |
5.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 类型,作为位置存储(而不是代码本身)。 |
5.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)
5.6 environments(环境)
MyBatis 可以配置成适应多种环境,例如,开发、测试和生产环境需要有不同的配置;
尽管可以配置多个环境,每个 SqlSessionFactory 实例只能选择其一。
虽然,这种方式也可以做到很方便的分离多个环境,但是实际使用场景下,我们更多的是选择使用spring来管理数据源,来做到环境的分离。
5.6.1 方法一:default
添加一个test(测试)环境,并在default参数中指向test环境。
5.6.2 方法二:build方法
5.7 Mappers
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要定义 SQL 映射语句了。但是首先我们需要告诉 MyBatis 到哪里去找到这些语句。Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。
5.7.1 方式一:resource
在mybatis-config.xml引入项目目录下的映射文件:
5.7.2 方式二:file(不采用)
5.7.3 方式三:class
在mybatis-config.xml配置mapper接口的全路径:
这种配置方式,在全局配置文件中配置了mapper接口的全路径,并没有配置mapper接口的映射文件的位置。如果要让mybatis找到对应的映射文件,则必须满足一定的条件或规则:
- 映射文件和mapper接口在同一个目录下
- 文件名必须一致
- 映射文件的namespace必须和mapper接口的全路径保持一致
缺点: - java文件和xml映射文件耦合
- 每新增一个映射文件,就要在全局配置文件中引入
5.7.4 方式四:package
在mybatis-config.xml中,开启包扫描:
原理:扫描目标包目录下的mapper接口,并按照class的方式找到接口对应的映射文件。
缺点:
- 如果包的路径有很多
- mapper.xml和mapper.java没有分离。
6 Mapper XML 文件(映射文件)
6.1 CRUD标签
6.1.1 select
select – 书写查询sql语句
select中的几个属性说明:
id属性:当前名称空间下的statement的唯一标识。必须。要求id和mapper接口中的方法的名字一致。
resultType:将结果集映射为java的对象类型。必须(和 resultMap 二选一)
parameterType:传入参数类型。可以省略
6.1.2 insert
insert 的几个属性说明:
id:唯一标识,随便写,在同一个命名空间下保持唯一,使用动态代理之后要求和方法名保持一致
parameterType:参数的类型,使用动态代理之后和方法的参数类型一致
useGeneratedKeys:开启主键回写
keyColumn:指定数据库的主键
keyProperty:主键对应的pojo属性名
标签内部:具体的sql语句。
6.1.3 update
id属性:当前名称空间下的statement的唯一标识(必须属性);
parameterType:传入的参数类型,可以省略。
标签内部:具体的sql语句。
6.1.4 delete
delete 的几个属性说明:
id属性:当前名称空间下的statement的唯一标识(必须属性);
parameterType:传入的参数类型,可以省略。
标签内部:具体的sql语句。
6.2 parameterType传入参数
CRUD标签都有一个属性parameterType,statement通过它指定接收的参数类型。
接收参数的方式有两种:
- #{}预编译
- ${}非预编译(直接的sql拼接,不能防止sql注入)
参数类型有三种:
- 基本数据类型
- HashMap(使用方式和pojo类似)
- Pojo自定义包装类型
6.2.1 #{} ${}用法
场景:数据库有两个一模一样的表。历史表,当前表
查询表中的信息,有时候从历史表中去查询数据,有时候需要去新的表去查询数据。希望使用1个方法来完成操作。
在UserMapper接口中,添加根据表名查询用户信息的方法:
在UserMapper映射文件中,添加方法对应的statement:
输出(报错):
如果你要动态传入的字段名是表名,并且sql执行是预编译的,这显然是不行的,所以你必须改成非预编译的,也就是这样:
但是这并不是一种稳妥的解决方案,推荐使用@Param注解指定参数名,所以以上接口及映射文件可以改成如下:
6.2.2 多个参数
接口方法中的参数前,添加@Param注解指定参数名
6.2.3 HashMap
parameterType有三种类型的输入参数:
- 基本数据类型
- hashMap
- pojo包装类
前面已经使用了基本数据类型和pojo类型的参数,那么hashMap这种类型的参数怎么传递参数呢?
其实,它的使用方式和pojo有点类似,简单类型通过#{key}或者 k e y ,复杂类型通过 {key},复杂类型通过 key,复杂类型通过{key.属性名}或者#{key.属性名}
6.2.4 面试题(#、$区别)
6.3 resultMap
6.3.1 解决列名和属性名不一致
查询数据的时候,查不到userName的信息,原因:数据库的字段名是user_name,而POJO中的属性名字是userName
两端不一致,造成mybatis无法填充对应的字段信息。修改方法:在sql语句中使用别名
- 解决方案1:在sql语句中使用别名
- 解决方案2:参考驼峰匹配 — mybatis-config.xml 的时候
- 解决方案3:resultMap自定义映射
6.3.2 sql片段
- 用法1
sql标签可以定义一个sql片段,在需要使用该sql片段的地方,通过include标签来使用,如改造根据表名查询用户信息:
- 用法2
很多时候同一个sql片段,可能在很多映射文件里都有使用,这就需要添加一个映射文件,用来统一定义sql片段。
如下,在resource目录下新增CommonSQL.xml文件:
内容如下:注意namespace
<?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>
使用:
7 动态sql
7.1 if
<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>
7.2 choose when otherwise
<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>
7.3 where
<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>
7.4 set
<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>
7.5 foreach
<select id="queryUserListByIds" resultType="User">
select * from tb_user where id in
<!--
foreach:遍历集合
collection:接收的集合参数
item:遍历的集合中的一个元素
separator:分隔符
open:以什么开始
close:以什么结束
-->
<foreach collection="list" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
8 缓存
执行相同的sql语句和参数,mybatis不进行执行sql,而是从缓存中命中返回。
8.1 一级缓存
在mybatis中,一级缓存默认是开启的,如果要关闭,参数localCacheScope设置为STATEMENT,作用域:在同一个sqlSession下。
@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);
}
使用: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);
}
执行update、insert、delete的时候,会清空缓存。
@Test
public void testCache(){
User user1 = this.userMapper.queryUserById(1l);
System.out.println(user1);
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);
}
8.2 二级缓存
作用域:
- 同一个mapper的namespace,同一个namespace中查询sql可以从缓存中命中。
- 跨sqlSession,不同的SqlSession可以从二级缓存中命中
如何开启
- 在映射文件中,添加标签
- 在全局配置文件中,设置cacheEnabled参数,默认已开启。
注意:
- 由于缓存数据是在sqlSession调用close方法时,放入二级缓存的,所以第一个sqlSession必须先关闭。
- 二级缓存的对象必须序列化,例如:User对象必须实现Serializable接口。
8.2.1 开启二级缓存方法1
开启二级缓存,在映射文件(UserMapper.xml)中添加:
@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);
}
8.2.2 开启二级缓存方法2
在mybatis-config.xml配置中:
<settings>
<!-- 行为参数,name:参数名,value:参数值,默认为false,true:开启驼峰匹配,即从经典的数据库列名到经典的java属性名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 关闭二级缓存,默认是开启,false:关闭 -->
<setting name="cacheEnabled" value="false"/>
</settings>
9 高级查询
9.1 表关系说明
需求说明:
9.2 一对一查询
需求:查询出订单信息,并查询出下单人信息
1. 脚本sql
select * from tb_order t1 LEFT JOIN tb_user t2 on t1.user_id = t2.id where t1.id = '1';
2. Order添加User属性
3. Mapper文件
<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>
9.3 一对多查询
查询订单,查询出下单人信息并且查询出订单详情。
思路:
订单 : 订单详情 = 1 : n(体现在pojo对象中,就是在Order对象中添加OrderDetail对象的集合)
1. 脚本
2. 在Order添加OrderDetail
3. Mapper文件
<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>
9.4 多对多查询
查询订单,查询出下单人信息并且查询出订单详情中的商品数据。
思路:
- 订单:订单详情 = 1 : n(体现在pojo对象中就是在Order对象中添加OrderDetail对象的集合)
- 订单详情:商品 = 1 : 1(体现在pojo对象中就是在OrderDetail对象中添加Item对象)
1. 脚本
2. 在Orderdetail添加Item
3. Mapper文件
<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>
9.5 resultMap的继承
9.6 高级查询整理
resultMap:
- type 结果集对应的数据类型
- id 唯一标识,被引用的时候,进行指定
- autoMapping 开启自动映射
- extends 继承
子标签:
- association:一对一的映射
- property 定义对象的属性名
- javaType 属性的类型
- autoMapping 开启自动映射
- collection:一对多的映射
- property 定义对象的属性名
- javaType 集合的类型
- ofType 集合中的元素类型
- autoMapping 开启自动映射
10 延迟加载
10.1 改造一对一查询
设置参数 | 描述 | 有效值 | 默认值 |
---|---|---|---|
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 | true 或 false | false |
aggressiveLazyLoading | 当启用时,带有延迟加载属性的对象的加载与否完全取决于对任意延迟属性的调用;反之,每种属性将会按需加载。 | true 或 false | true |
分析:
采用之前的配置方式(参考高级查询),肯定是不能做到延迟加载的,因为咱是通过一个查询sql直接查询出所有的数据。为了测试延迟加载的效果,必须改造高级查询的配置,使Order的查询和User或者OrderDetail的查询分开。只有当我们访问Order对象的User或者OrderDetail属性时,才去执行User或者OrderDetail的查询。
1. Mapper文件
2. 注意
处理组合键时,需要传递多个参数,可以使用column=”{prop1=col1, prop2=col2, prop3=col3…}”,设置多个列名传入到嵌套查询语句,mybatis会把prop1,prop2,prop3设置到目标嵌套的查询语句中的参数对象中。
子查询中,必须通过prop1,prop2,prop3获取对应的参数值,你也可以使用这种方式指定参数名例如:
3. 案例
分析:红色部分为查询Order信息的日志,绿色为查询Order中的User信息的日志。已经成功拆分出两个子查询,并且查询出了带有User信息的Order信息。
10.2 开启延迟查询
在mybatis-config.xml中配置行为参数:
修改测试用例,注释掉打印Order信息的这行代码:
明延迟加载需要cglib的支持。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
11 如果sql语句中出现’<’的解决方案
- 使用xml中的字符实体
- 使用<![CDATA[ < ]]>