整理了有关mybatis框架的知识,方便查阅和学习
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatement、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
Mybatis优势
JDBC 问题总结
JDBC编程步骤:
设置sql语句参数(preparedStatement)
释放资源(resultSet、preparement、connection)
主要问题:
数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。
Mybatis VS Hibernate
Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。
Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。
MyBatis架构
mybatis配置,SqlMapConfig.xml
,mybatis的全局配置文件,配置了mybatis的运行环境等信息。mapper.xml
文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml
中加载。
通过mybatis
环境等配置信息构造SqlSessionFactory
即会话工厂
由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession
进行。
mybatis
底层自定义了Executor执行器接口操作数据库,Executor
接口有两个实现,一个是基本执行器、一个是缓存执行器。
MappedStatement
也是mybatis
一个底层封装对象,它包装了mybatis
配置信息 及sql映射信息 等。mapper.xml
文件中一个sql对应一个 MappedStatement
对象 ,sql的id即是 MappedStatement
的id 。
MappedStatement
对sql执行输入参数进行定义,包括HashMap
、基本类型、pojo
,Executor
通过MappedStatement
在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement
设置参数。
MappedStatement
对sql执行输出结果进行定义,包括HashMap
、基本类型、pojo
,Executor
通过MappedStatement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
环境构建
官方网址:https://github.com/mybatis/mybatis-3/releases
mybatis-3.2.7.jar----mybatis的核心包
mybatis-3.2.7.pdf----mybatis使用手册
添加maven依赖 :
复制 <!--添加数据库驱动-->
<!--添加数据库连接池-->
<!--添加mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.2</version>
</dependency>
mybatis默认使用log4j进行日志输出,需要在resources下添加log4j配置文件 log4j.properties
复制 # Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
核心配置文件编写:
在classpath/resources下创建SqlMapConfig.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>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理-->
<transactionManager type="JDBC" />
<!-- 数据库连接池-->
<dataSource type="POOLED">
<property name="driver" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" />
<property name="username" value="jsd1703" />
<property name="password" value="jsd1703" />
</dataSource>
</environment>
</environments>
<!--加载sql映射文件,否则不会生效-->
<mappers>
<mapper resource="User.xml"/>
</mappers>
</configuration>
配置sql映射文件
User.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="user">
<!--
id sql的唯一标识
parameterType 传入的参数的类型
resultType 返回结果集类型
#{} 占位符,如果是常见的基本类型,那么中的变量名称可以随意设置
-->
<!--查询单个-->
<select id="findById" parameterType="java.lang.Integer" resultType="me.feathers.pojo.User">
SELECT *
FROM WEB_USER
WHERE id = #{id}
</select>
<!--查询多个-->
<!--注意,在sql语句上不可以使用/**/或者-- 注释,否则会被解析为ognl表达式,报 org.apache.ibatis.ognl.ExpressionSyntaxException: Malformed OGNL expression: [org.apache.ibatis.ognl.ParseException-->
<select id="findAll" parameterType="java.lang.String"
resultType="me.feathers.pojo.User">
SELECT *
FROM WEB_USER
WHERE username LIKE '%${value}%'
</select>
<!--插入-->
<!--如果传入的是pojo类型,那么#{}中必须是pojo的属性-->
<!--
selectKey:用来生成主键
keyProperty:返回的主键存储在pojo中的哪个属性,
order:selectKey的执行顺序(相对于insert语句来说) 有berfore和after两种选择,oracle的序列需要先生成,所以这里使用berfore
而mysql的自增原理执行完insert语句之后才能将主键生成,mysql需要使用after
-->
<insert id="save" parameterType="me.feathers.pojo.User">
<selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Long">
SELECT "sq_web_user".nextval FROM dual
</selectKey>
INSERT INTO WEB_USER (ID, PASSWORD, STATE, USERNAME)
VALUES (${id},#{password}, #{state}, #{username})
</insert>
<delete id="delById" parameterType="java.lang.Integer">
DELETE FROM WEB_USER
WHERE ID = #{id}
</delete>
<update id="update" parameterType="me.feathers.pojo.User">
update WEB_USER set username=#{username}, PASSWORD=#{password}, STATE=#{state}
where id=#{id}
</update>
</mapper>
注意:
#{}
表示一个占位符号,通过#{}
可以实现preparedStatement
向占位符中设置值,自动进行java类型和jdbc类型转换,#{}
可以有效防止sql注入。#{}
可以接收简单类型值或pojo属性值。 如果parameterType
传输单个简单类型值,#{}
括号中可以是value或其它名称。
${}
表示拼接sql串,通过${}
可以将parameterType
传入的内容拼接在sql中且不进行jdbc类型转换, ${}
可以接收简单类型值或pojo属性值,如果parameterType
传输单个简单类型值,${}
括号中只能是value。
mysql的主键生成策略:
使用uuid:select uuid()
使用 ast_insert_id()寒素, select AST_INSERT_ID()
,返回auto_increment自增列新记录id值。
书写Java代码
在sql映射文件中已经包含了增删改查的sql语句,现在可以通过代码完成增删改查的操作了。
复制 // 查找单个
@Test
public void testFindById() throws IOException {
// 1. 加载配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 使用核心配置文件拿到会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3. 使用工厂获取会话
SqlSession s = factory.openSession();
// 4. 使用session进行操作
// 参1: sql语句,namespace.sqlId
User u = s.selectOne("user.findById", 1);
System.out.println(u+"---------");
// 5. 关闭会话
s.close();
}
查找多个:
复制 List<User> us = s.selectList("user.findAll", "ro");
插入:
复制 User user = s.selectOne("user.findById", 4);
int count = s.delete("user.delById", user);
s.commit(); //注意提交
更新:
复制 User user = s.selectOne("user.findById", 4);
user.setPassword("456789");
user.setUsername("李四");
int count = s.update("user.update", user);
s.commit();
删除:
复制 int count = s.delete("user.delById", 4); // count为1 代表删除了一条
selectOne查询一条记录,如果使用selectOne查询多条记录则抛出异常:
复制 org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70)
MyBatis 解决的问题
数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在SqlMapConfig.xml
中配置数据链接池,使用连接池管理数据库链接。
Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决:将Sql语句配置在XXXXmapper
.xml文件中与java代码分离。
向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。
MyBatis常见类的作用
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder
用于创建SqlSessionFacoty
,SqlSessionFacoty
一旦创建完成就不需要SqlSessionFactoryBuilder
了,因为SqlSession
是通过SqlSessionFactory
生产,所以可以将SqlSessionFactoryBuilder
当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。
SqlSessionFactory
是一个接口,接口中定义了openSession()的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。
SqlSession
SqlSession是一个面向用户的接口,封装了对数据库的操作,如:查询、插入、更新、删除等。
通过SqlSessionFactory
创建SqlSession
,而SqlSessionFactory
是通过SqlSessionFactoryBuilder
进行创建。
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
打开一个 SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。如下:
复制 SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}
使用MyBatis开发DAO
使用MyBatis开发Dao有两种方式:
原始Dao开发
interface: UserDao
复制 public interface UserDao {
void save(User user);
User findById(Integer id);
List<User> findAll(String name);
void update(User user);
void delById(Integer id);
}
实现:
复制 /**
* @author Feathers
* @create 2017-07-17-14:59
*/
public class UserDaoImpl implements UserDao {
private SqlSessionFactory ssf;
public UserDaoImpl(SqlSessionFactory ssf){
this.ssf = ssf;
}
@Override
public void save(User user) {
SqlSession s = ssf.openSession();
try {
s.insert("user.save", user);
s.commit();
} finally {
s.close();
}
}
@Override
public User findById(Integer id) {
SqlSession s = ssf.openSession();
User user;
try {
user = s.selectOne("user.findById", id);
s.commit();
} finally {
s.close();
}
return user;
}
@Override
public List<User> findAll(String name) {
SqlSession s = ssf.openSession();
List<User> users;
try {
users = s.selectList("user.findAll", name);
s.commit();
} finally {
s.close();
}
return users;
}
@Override
public void update(User user) {
SqlSession s = ssf.openSession();
try {
s.update("user.update", user);
s.commit();
} finally {
s.close();
}
}
@Override
public void delById(Integer id) {
SqlSession s = ssf.openSession();
try {
s.delete("user.delById", id);
s.commit();
} finally {
s.close();
}
}
}
测试类:
复制 import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import me.feathers.dao.UserDao;
import me.feathers.dao.UserDaoImpl;
import me.feathers.pojo.User;
/**
* @author Feathers
* @create 2017-07-17-15:12
*/
public class UserDaoTest {
private UserDao dao;
@BeforeTest
public void test() throws IOException {
// 1. 加载配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 使用核心配置文件拿到会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
dao = new UserDaoImpl(factory);
}
@Test(priority = 1)
public void testSave() {
dao.save(new User("赵六", "123456", 0));
}
@Test(priority = 2)
public void testFindById(){
User byId = dao.findById(4);
System.out.println(byId);
}
@Test(priority = 3)
public void testUpdate() {
User byId = dao.findById(4);
byId.setUsername("王五");
dao.update(byId);
}
@Test(priority = 4)
public void testFindAll(){
List<User> all = dao.findAll("");
System.out.println(all);
}
@Test(priority = 5)
public void testDelById(){
dao.delById(4);
}
}
原始Dao开发中存在以下问题:
Dao方法体存在重复代码:通过SqlSessionFactory创建SqlSession,调用SqlSession的数据库操作方法
调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不利于开发维护。
MyBatis模板工具类
复制 /*MyBatisUtil,负责加载配置文件,创建sqlSessionFactory,获取sqlSession*/
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
return sqlSessionFactory == null ? null : sqlSessionFactory.openSession();
}
public static void close(SqlSession sqlSession){
if (sqlSession != null) {
sqlSession.close();
}
}
}
/*MyBatis回调接口,负责完成执行的操作*/
import org.apache.ibatis.session.SqlSession;
public interface MyBatisCallback {
Object doInMyBatis(SqlSession ses);
}
package me.feathers.online.util;
import org.apache.ibatis.session.SqlSession;
/**
* mybatis模板工具类
*
* @author Feathers
* @create 2017-07-30-10:51
*/
public class MyBatisTemplate {
public static Object excute(MyBatisCallback callback) {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
Object o = null;
try {
o = callback.doInMyBatis(sqlSession);
} finally {
MyBatisUtil.close(sqlSession);
}
return o;
}
}
/**调用实例**/
@Override
public User findById(Long id) {
return (User) MyBatisTemplate.excute(ses -> {
return ses.selectOne("user.findById", id);;
});
}
如果你要添加Transaction,请修改Template类
Mapper动态代理方式
开发规范
Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper接口开发需要遵循以下规范:
Mapper.xml
文件中的namespace
与Mapper
接口的类路径相同。
Mapper
接口方法名和Mapper.xml
中定义的每个statement
的id相同
Mapper
接口方法的输入参数类型和mapper.xml
中定义的每个sql 的parameterType
的类型相同
Mapper
接口方法的输出参数类型和mapper.xml
中定义的每个sql的resultType
的类型相同
定义Mapper接口
UserMapper.java:
复制 public interface UserMapper {
void save(User user);
User findById(Integer id);
List<User> findAll(String name);
void update(User user);
void delById(Integer id);
}
Mapper.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">
<!--1. Mapper.xml文件中的namespace与mapper接口的类路径相同。-->
<mapper namespace="me.feathers.mapper.UserMapper">
<!--2. Mapper接口方法名和Mapper.xml中定义的每个statement的id相同 -->
<!--3. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同-->
<!--4. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同-->
<select id="findById" parameterType="java.lang.Integer" resultType="me.feathers.pojo.User">
SELECT *
FROM WEB_USER
WHERE id = #{id}
</select>
<!--注意,这里结果集类型不是java.util.List-->
<select id="findAll" parameterType="java.lang.String"
resultType="me.feathers.pojo.User">
SELECT *
FROM WEB_USER
WHERE username LIKE '%${value}%'
</select>
<insert id="save" parameterType="me.feathers.pojo.User">
<selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Long">
SELECT "sq_web_user".nextval FROM dual
</selectKey>
INSERT INTO WEB_USER (ID, PASSWORD, STATE, USERNAME)
VALUES (${id},#{password}, #{state}, #{username})
</insert>
<delete id="delById" parameterType="java.lang.Integer">
DELETE FROM WEB_USER
WHERE ID = #{id}
</delete>
<update id="update" parameterType="me.feathers.pojo.User">
update WEB_USER set username=#{username}, PASSWORD=#{password}, STATE=#{state} where
id=#{id}
</update>
</mapper>
加载映射文件
复制 <!-- 加载映射文件 -->
<mappers>
<mapper resource="Mapper.xml"/>
</mappers>
编写测试类
复制 import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import me.feathers.dao.UserDao;
import me.feathers.dao.UserDaoImpl;
import me.feathers.mapper.UserMapper;
import me.feathers.pojo.User;
/**
* @author Feathers
* @create 2017-07-17-15:12
*/
public class UserMapperTest {
SqlSessionFactory factory;
@BeforeTest
public void test() throws IOException {
// 1. 加载配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 使用核心配置文件拿到会话工厂
factory = new SqlSessionFactoryBuilder().build(in);
}
@Test(priority = 1)
public void testSave() {
SqlSession session = factory.openSession();
//获取mapper接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用代理对象方法
userMapper.save(new User("赵六", "123456", 0));
}
@Test(priority = 2)
public void testFindById(){
SqlSession session = factory.openSession();
//获取mapper接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
User byId = userMapper.findById(4);
System.out.println(byId);
}
@Test(priority = 3)
public void testUpdate() {
SqlSession session = factory.openSession();
//获取mapper接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用代理对象方法
User byId = userMapper.findById(4);
byId.setUsername("王五");
userMapper.update(byId);
}
@Test(priority = 4)
public void testFindAll(){
SqlSession session = factory.openSession();
//获取mapper接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用代理对象方法
List<User> all = userMapper.findAll("");
System.out.println(all);
}
@Test(priority = 5)
public void testDelById() {
SqlSession session = factory.openSession();
//获取mapper接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用代理对象方法
userMapper.delById(4);
}
}
SqlMapConfig.xml
配置内容:
properties
将数据库信息提取到 db.properties
中,然后引入到myBatis的核心配置文件中进行配置
复制 <!--引入db.properties-->
<properties resource="config/db.properties"/>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理-->
<transactionManager type="JDBC" />
<!-- 数据库连接池-->
<dataSource type="POOLED">
<property name="driver" value="${driverClass}" />
<property name="url" value="${url}" />
<property name="username" value="${user}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
MyBatis 将按照下面的顺序来加载属性:
在 properties 元素体内定义的属性首先被读取。
然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。
typeAliases
类型别名,MyBatis支持的类型别名:
作用:
在MyBatis中的类型属性可以使用一上的类型别名替代,比如parameterType="int"
就同parameterType="java.lang.Integer"
是一样的
好处,简化了开发
自定以类型别名:
类型别名的定义在MyBatis SqlMapConfig.xml
的核心配置文件中!!!
复制 <typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="user" type="me.feathers.pojo.User"/>
<!-- 批量别名定义,扫描整个包下的类,每个类的别名为类的类名(首字母大写或小写都可以) -->
<!--<package name="me.feathers.pojo"/>-->
<!--<package name="其它包"/>-->
</typeAliases>
mappers
配置映射器。
使用相对类路径的资源:<mapper resource="sqlmap/User.xml" />
使用mapper接口类路径:<mapper class="me.feathers.mapper.UserMapper"/>
自动注册指定包下的所有mapper接口,<package name="me.feathers.mapper"/>
输入映射和输出映射
Mapper.xml映射文件中定义了操作数据库的sql,每个sql是一个statement,映射文件是mybatis的核心。
parameterType 输入类型
传递pojo包装类型:
开发中通过pojo传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
UserVo (Value Object) :
复制 // 一定要提供getter/setter方法,ognl 表达式底层和el表达式相同,是使用getter setter方法获取属性的
public class UserVo {
private User user;
public void setUser(User user) {
this.user = user;
}
public User getUser() {
return user;
}
}
Mapper接口方法:
复制 List<User> findUserByUserVo(UserVo userVo);
Mapper配置文件:
复制 <select id="findUserByUserVo" parameterType="me.feathers.pojo.vo.UserVo"
resultType="user">
SELECT * FROM WEB_USER where username like '%${user.username}%'
</select>
<!--这里使用ognl取出查询参数,并拼接到sql语句中-->
resultType 输出类型
这里不再演示,请查看上面的代码。
resultMap
resultType可以将查询结果映射为pojo ,但需要pojo的属性名和sql查询的列名一致方可映射成功。如果查询字段与pojo的属性名不一致,可以通过resultMap将字段名和属性名一一对应,完成映射。
复制 <resultMap type="user" id="userListResultMap">
<id column="id_" property="id"/>
<result column="username_" property="username"/>
<result column="password_" property="password"/>
</resultMap>
<id/>
标签:此属性表示查询结果集的唯一标识,非常重要。如果是多个字段为复合唯一约束则定义多个<id />
。
Column
:表示sql查询出来的字段名。Column和property放在一块儿表示将sql查询出来的字段映射到指定的pojo类属性上。
<result />
:普通结果,即pojo的属性。
如果列名和表名一致,可以不写。
动态sql
通过mybatis提供的各种标签方法实现动态拼接sql。
if
之前在传递参数到sql中时,如果参数为null,则会发生错误,为了避免查询参数不合法的情况,我们可以使用if进行有效性判断。
复制 <select id="findAll" parameterType="java.lang.String"
resultType="User">
SELECT *
FROM WEB_USER
WHERE 1=1
<if test="username!=null and username!=''">
AND username LIKE '%${value}%'
</if>
</select>
注意,字符串参数一定要进行空字符串判断
where
上面使用了1=1
来保证sql语句的完成性,我们也可以使用where标签替代。
复制 <select id="findAll" parameterType="java.lang.String"
resultType="User">
SELECT *
FROM WEB_USER
<where>
<if test="username!=null and username!=''">
AND username LIKE '%${value}%'
</if>
</where>
</select>
备注:<where />
可以自动处理第一个and。
foreach
当向sql传递数组或List,mybatis使用foreach进行解析:
复制 SELECT * FROM USERS WHERE username LIKE '%张%' AND id IN (10,89,16)
sql片段:
复制 <if test="ids!=null and ids.size>0">
<!--ids 要遍历的集合,将集合遍历,拼接为sql字符串 open 开始拼接 close 结束拼接, separator 分隔符, 得到的结果为 and id in (10,89,16)-->
<foreach collection="ids" open=" and id in(" close=")" item="id" separator="," >
#{id}
</foreach>
</if>
Sql 片段
xml中的sql可能会发生大量重复的sql片段,我们可以将他们抽取到sql标签中,然后使用include引用这些sql片段。
复制 <select id="findUserList" parameterType="user" resultType="user">
select * from user
<where>
<if test="id!=null and id!=''">
and id=#{id}
</if>
<if test="username!=null and username!=''">
and username like '%${username}%'
</if>
</where>
</select>
抽出条件
复制 <sql id="query_user_where">
<if test="id!=null and id!=''">
and id=#{id}
</if>
<if test="username!=null and username!=''">
and username like '%${username}%'
</if>
</sql>
使用include引用:
复制 <select id="findUserList" parameterType="user" resultType="user">
select * from user
<where>
<include refid="query_user_where"/>
</where>
</select>
关联查询
一对一关联查询
预备工作
复制 drop table wife;
drop table husband;
create table husband(
hus_id number(19) primary key,
hus_name varchar2(20),
age number(3),
birthday date
);
create table wife(
wife_id number(19) primary key,
wife_name varchar2(255),
age number(3),
birthday date,
hus_id number(19),
foreign key(hus_id) references husband(hus_id)
);
insert into husband(hus_id, hus_name, age, birthday) values(1, '张山', 24, sysdate);
insert into husband(hus_id, hus_name, age, birthday) values(2, '李斯', 25, sysdate);
insert into wife(wife_id, wife_name, age, birthday, hus_id) values(1, '小红', 24, sysdate, 2);
insert into wife(wife_id, wife_name, age, birthday, hus_id) values(2, '丽丽', 23, sysdate, 1);
有两张表,分别为妻子和丈夫, 属于一对一的关系。要求根据丈夫查找对应的妻子。
方式一
一对一自动映射,试用resultType,定义夫妻信息po类,该类包含了丈夫和妻子的信息,即所要查的信息。
现在我们要查询所有的夫妻对,有如下sql语句:
复制 select w.wife_name, w.age wife_age, hus.* from husband hus join wife w on w.hus_id = HUS.hus_id; --给age起个别名方便使用
定义sql映射:
复制 <select id="findAll" resultType="me.feathers.pojo.WifeAndHusband">
SELECT
w.wife_name,
w.age wife_age,
hus.*
FROM husband hus
JOIN wife w ON w.hus_id = HUS.hus_id
</select>
/**使用resultType,定义夫妻信息类,此po类中包括了丈夫信息和妻子信息**/
/**Husband类**/
public class Husband {
private Long hus_id;
private String hus_name;
private int age;
private Date birthday;
// setter / getter
}
/**夫妻类继承husband类**/
public class WifeAndHusband extends Husband {
private String wife_name;
private int age;
// setter / getter
}
这样就将所需要的信息封装到实体类中了。
测试类:
复制 @Test
public void findAll() {
SqlSession session = factory.openSession();
//获取mapper接口的代理对象l
HusbandMapper husbandMapper = session.getMapper(HusbandMapper.class);
//调用代理对象方法
List<WifeAndHusband> all = husbandMapper.findAll();
all.forEach(System.out :: println);
}
这种方式在开发中最为常用,不仅可以处理这种一对一的表关系,根据订单查用户也是属于一对一的查询,此种方式也是试用的。
方式二
一对一手动映射,定义一个resultMap,用于映射一对一查询结果。
sql语句仍然不变:select w.wife_name, w.age wife_age, hus.* from husband hus join wife w on w.hus_id = HUS.hus_id;
复制 /**定义husband 和 wife**/
/**Husband类**/
public class Husband {
private Long hus_id;
private String hus_name;
private int age;
private Date birthday;
// setter / getter
}
/**Wife类**/
public class Wife {
private Long wife_id;
private String wife_name;
private int age;
private Date birthday;
private Husband husband; // 在wife中维护一个husband对象
// setter / getter
}
<!-- 定义resultMap -->
<resultMap id="wifeAndHus" type="me.feathers.pojo.Wife">
<id property="wife_id" column="wife_id"/>
<result property="age" column="age"/>
<result property="wife_name" column="wife_name"/>
<result property="birthday" column="birthday"/>
<!-- 一对一关联映射 -->
<association property="husband" javaType="me.feathers.pojo.Husband">
<id property="hus_id" column="hus_id"/>
<result property="age" column="age"/>
<result property="hus_name" column="hus_name"/>
<result property="birthday" column="birthday"/>
</association>
</resultMap>
<select id="findAll1" resultMap="wifeAndHus">
SELECT
w.wife_name,
w.age wife_age,
hus.*
FROM husband hus
JOIN wife w ON w.hus_id = HUS.hus_id
</select>
association:表示进行关联查询单条记录
property:表示将关联查询数据库column列的值放入到property属性中
javaType:表示关联查询的结果类型析
一对多级联查询
一对多关联映射同一对一关联映射的方式二相同,试用自定义的ResultMap
复制 <!-- 一对多关联映射 这里会映射为orders集合 -->
<collection property="orders" ofType="cn.itheima.po.Orders">
<id property="id" column="oid"/>
<!--用户id已经在user对象中存在,此处可以不设置-->
<!-- <result property="userId" column="id"/> -->
<result property="number" column="number"/>
<result property="createtime" column="createtime"/>
<result property="note" column="note"/>
</collection>
缓存
Mybatis的缓存是针对SQL查询结果的缓存,如果执行了两个完全相同的SQL(同一个SQL,同样的SQL参数),在有缓存的情况下,将不会再执行一次SQL,而是直接使用缓存的结果。
一级缓存
一级缓存是是SqlSession级别的缓存,也就是说,如果Mapper处于同一个namespace下,并且处于同一个SqlSession下,缓存才会生效。
在一级缓存下,所有的缓存数据存储到SqlSession对象中。
复制 UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.findById(123); // 打印了sql日志
UserMapper userMapper2 = session.getMapper(UserMapper.class);
User user2 = userMapper2.findById(123); // 没有打印sql日志,说明sql只执行了一次
System.out.println(userMapper == userMapper2); // false
System.out.println(user == user2); // true
一级缓存的原理:
实际缓存结构是一个map,放置在SqlSession中
当有一个SQL需要缓存时,将会按照mapperID + offset + limit + sql + sql所有参数
的形式生成key
当同一个SqlSession再次发出相同的SQL,就从缓存中取出数据。
如果两次相同的操作中存在commit
操作(包括增、删、改),SqlSession的一级缓存区域将会被全部清空。下次再去缓存中查询不到,将会从数据库查询。
二级缓存
**二级缓存是以namespace为标记的缓存,可以是由一个SQLSessionFactory创建的SqlSession之间共享的缓存。**默认不开启。
二级缓存是Mapper级别的
在二级缓存下,每个namespace都会生成一个Cache对象,独立于SqlSession,并与所有的SqlSession共享。
开启二级缓存(总开关):
复制 <settings>
<setting name="cacheEnabled" value="false"/>
<setting name="defaultStatementTimeout" value="5"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="useGeneratedKeys" value="true"/>
</settings>
分开关,在要开启二级缓存的mapper中添加如下标签:
复制 <mapper namespace="xxx">
<cache />
</mapper>
二级缓存不一定使用内存存储,且查询结果类必须实现Serializable
接口。
二级缓存的原理:
MyBatis的二级缓存是通过CacheExecutor实现的,CacheExecutor是Executor的代理对象
所有的查询操作,在CacheExecutor中都会匹配缓存中是否存在,不存在则查询数据库
每一个Mapper都会生成对应的一个MapperCache对象,用于缓存数据,他也是一个Map结构
不同的Sql在map中使用如下格式的key存储:MapperId + offset + limit + sql + sql所有参数
三级缓存
一级缓存与二级缓存都是Mybatis自带的缓存机制,解决不了在分布式环境下的缓存问题。所以可以使用三级缓存,三级缓存一般使用第三方缓存中间件(Memcached、Redis、ehCache)。
比如,使用ehcache作为缓存机制,就需要引入第三方jar mybatis-ehcache
。
Spring整合MyBatis
SqlSessionFactory
对象应该放到spring容器中作为单例存在。
传统dao的开发方式中,应该从spring容器中获得sqlsession
对象。
Mapper代理形式中,应该从spring容器中直接获得mapper
的代理对象。
数据库的连接以及数据库连接池事务管理都交给spring容器来完成。
复制 <!--sqlMapConfig.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>
<typeAliases>
<package name="cn.itcast.mybatis.pojo"/>
</typeAliases>
<mappers>
<mapper resource="sqlmap/User.xml"/>
</mappers>
</configuration>
<!-- applicationContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 加载配置文件 -->
<context:property-placeholder location="classpath:db.properties" />
<!-- 数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="10" />
<property name="maxIdle" value="5" />
</bean>
<!-- mapper配置 -->
<!-- 让spring管理sqlsessionfactory 使用mybatis和spring整合包中的 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据库连接池 -->
<property name="dataSource" ref="dataSource" />
<!-- 加载mybatis的全局配置文件 -->
<property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml" />
</bean>
</beans>
db.properties 省略
参考模块代码:spring-mybatis
逆向工程
mybatis-generator
Mybatis需要程序员手动编写SQL,表过多时,会出现大量的重复工作,所以,mybatis提供了逆向工程。可以根据数据表生成 Mapper接口,SqlMapper映射文件,实体类等。
Maven插件方式具体操作:
添加maven依赖mybatis-generator-core
依赖maven插件mybatis-generator-maven-plugin
使用 idea mybatis plugins 插件 new 一个Mybatis GeneratorFile
执行maven插件即可mybatis-generator:generate
mybatis-generator-gui
可视化界面generator: https://github.com/zouzg/mybatis-generator-gui.git