Mybatis环境搭建
前期准备
创建数据库
create table user(
id int not null auto_increment,
name varchar(30),
gender varchar(6),
age int,
birthday date,
primary key(id)
);
创建maven Java项目
打开Idear–>选择File–>选择new–>选择project
导入依赖
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
搭建项目环境
编写User实体类
package cn.offcn.entity;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
private Integer id;
private String name;
private String gender;
private Integer age;
private Date birthday;
public User() {}
public User(String name, String gender, Integer age, Date birthday) {
this.name = name;
this.gender = gender;
this.age = age;
this.birthday = birthday;
}
//生成getter和setter方法
}
编写持久层User接口
public interface UserDao {
/**
* 查询所有User对象
* @return 返回List集合
*/
public List<User> queryAllUsers();
}
编写持久层User接口映射文件UserDao.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="cn.offcn.dao.UserDao">
<!--配置查询的sql语句-->
<select id="queryAllUsers" resultType="cn.offcn.entity.User">
select * from user
</select>
</mapper>
编写mybatis主配置文件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>
<!-- 配置 mybatis的环境 -->
<environments default="development">
<!-- 配置环境 -->
<environment id="development">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的信息:用的是数据源【连接池】-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis001"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 注册UserDao接品映射文件位置 -->
<mappers>
<mapper resource="cn/offcn/dao/UserDao.xml"/>
</mappers>
</configuration>
Mybatist入门案例
入门案例编写
编写工具类读取配置文件
利用Mybatis对数据库进行操作时,都需要产生Mybatis总配置文件的输入流对象、构建SqlSessionFactoryBuilder对象、调用build方法进产生SqlSessionFactory工厂对象、调用工厂类openSession()方法创建SqlSession对象,代码冗余量大,步聚颇有繁琐,所以我们对产生SqlSession对象利用工具类进行优化。
package cn.offcn;
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.InputStream;
public class MyBatisUtils {
//定义静态变量sqlSessionFactory
private static SqlSessionFactory sqlSessionFactory;
//创建静态块,当MyBatisUtils类被加载时,自动执行该静态块,始初数据。
static{
try{
//获取mybatis主配置文件SqlMapperConfig.xml的输入流
InputStream inputStream= Resources.getResourceAsStream("SqlMapperConfig.xml");
//创建SqlSessionFactoryBuilder构建者对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//调用build方法返回SqlSessionFactory工厂对象
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
}catch (Exception e){
e.printStackTrace();
System.out.println("初始化数据失失败"+e.getMessage());
}
}
//创建getSqlSessionFactory方法返回SqlSessionFactory对象
public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
//创建一个SqlSession对象并返回
public static SqlSession getSession(){
return sqlSessionFactory.openSession();
}
//关闭SqlSession方法
public static void close(SqlSession session){
if(session!=null) session.close();
}
}
使用代理对象操作数据库
public class UserTest {
@Test
public void testQueryAllUsers() throws Exception{
//1.获取mybatis主配置文件SqlMapperConfig.xml的输入流
InputStream inputStream=Resources.getResourceAsStream("SqlMapperConfig.xml");
//2.创建SqlSessionFactoryBuilder构建者对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//3.调用build方法返回SqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
//4.调用sqlSessionFactory的openSession方法返回一个Session对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//5.调用SqlSession 创建 UserDao接口的代理对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
//6.调用代理对象的queryAllUsers方法查询所有User
List<User> users = userDao.queryAllUsers();
users.forEach(System.out::print);
//7.释放资源
sqlSession.close();
inputStream.close();
}
@Test
public void testQueryAllUsers2() throws Exception{
//调用MyBatisUtils工具创建SqlSession对象
SqlSession sqlSession = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
//调用代理对象的queryAllUsers方法查询所有User
List<User> users = userDao.queryAllUsers();
users.forEach(System.out::print);
//释放资源
sqlSession.close();
inputStream.close();
}
}
Mybatis执行原理分析
配置文件分析
1. 核心配置文件
<?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>
<!-- 配置 mybatis 的环境 -->
<environments default="development">
<!-- 配置的环境 -->
<environment id="development">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的信息:用的是数据源【连接池)】-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis001"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 注册UserDao接品映射文件位置 -->
<mappers>
<mapper resource="cn/offcn/dao/UserDao.xml"/>
</mappers>
</configuration>
核心配置文件参数详解:
(1)environment标签中的id属性值必须和environments标签中的default属性一致。
(2)事务管理器:
第一种采用JDBC事务类型,直接使用了 JDBC的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
第二种采用MANAGED事务类型,它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
(3) 数据源(dataSource)
dataSource元素使用标准的JDBC数据源接口来配置JDBC连接对象的资源。大多数 MyBatis 应用程序会按示例中的例子来配置数据源。 虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]")
UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的数据源引用。
(5) mapper中的resource属性用于指定映射文件的位置。mapper中有多个属性可以描述映射文件,分别为:
resource=“cn/offcn/dao/UserDao.xml” 文件在多级目录中要用分隔符“/”隔开。
class="cn.offcn.dao.UserDao" 指定UserDao接口文件位置,但此时UserDao.xml必须和接口处在同一个包中并且文件名要 相同。
2. 映射文件
<mapper namespace="cn.offcn.dao.UserDao">
<!--配置查询的sql语句-->
<select id="queryAllUsers" resultType="cn.offcn.entity.User">
select * from user
</select>
</mapper>
映射文件参数详解:
(1) namespace必须为接口的完全限定名(即包名+类名的格式)。
(2) select标签中的id必须和接口中声明的方法同名。
(3) 如果接口中方法有返回值,resyultType必须跟方法返回值一致并采用返回值的完全限定名来表示。
底层源码分析
1.利用Resources的getResourceAsStream方法读取mybatis核心配置文件,该配置文件中注册数据源【dataSource】和映射文件的位置【mappers标签中的mapper子标签的resource属性】。
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis001"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
<mappers>
<mapper resource="cn/offcn/dao/UserDao.xml"/>
</mappers>
2.展开映射文件【UserDao.xml】中定义了查询方法、查询时所封装结果类型和所要执行的sql语句。
<select id="queryAllUsers" resultType="cn.offcn.entity.User">
select * from user
</select>
3.使用构建者模式SqlSessionFactoryBuilder类的build方法创建SqlSessionFactory对象,在build方法中从字节输入流中解析数据。
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
//SqlSessionFactoryBuilder源码:
//SqlSessionFactoryBuilder的build方法
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
;
}
}
return var5;
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
//XMLConfigBuilder类的构造方法
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment,props);
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
this.commonConstructor(validation, variables, entityResolver);
this.document = this.createDocument(new InputSource(inputStream));
}
上述代码中我们可以看到创建了一个XMLConfigBuilder对象用来解析XML,把配置文件中所有标签及标签中属性值放封装到Configuration对象中并返回SqlSessionFactory对象。
4.调用SqlSessionFactory对象的openSession方法返回一个SqlSession对象,SqlSessionFactory是一个接口,我们找到它的实现类
DefaultSqlSessionFactory类,点击它的openSession方法,打开openSessionFromDataSource方法。
//DefaultSqlSessionFactory源码:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
//创建一个新事务,从环境中找到数据源。
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
//创建一个DefaultSqlSession对象
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
5.调用sqlSession.getMapper(UserDao.class)方法返回一个UserDao接口的代理类对象。
//DefaultSqlSession源码:
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
//Configuration类源码:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
//MapperRegistry类源码:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//MapperProxyFactory类源码:
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
//通过Proxy的newProxyInstance方法创建代理对象
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//MapperProxy类源码:
//invoke回调方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
//调用执行器excute方法
return mapperMethod.execute(sqlSession, args);
}
//MapperMethod类源码:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
执行原理图
MyBatis基于代理Dao实现CRUD操作
数据保存
定义数据添加的接口方法addUser
public class UserDao{
/**
新增添加用户方法
*/
public void addUser(User user);
}
配置文件中配置对应的sql
<insert id="addUser" parameterType="cn.offcn.entity.User">
insert into user(name,gender,age,birthday) values (#{name},#{gender},#{age},#{birthday})
</insert>
#{}为mybatis的占位符,如果方法中传递的参数为实体类类型,#{实体类属性}
添加测试方法
@Test
public void testAddUser() throws Exception{
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
//创建User对象
User user=new User("何晓","男",22,new Date());
//调用addUser方法进行保存
userDao.addUser(user);
//关闭连接
MyBatisUtils.close(session);
}
数据更新
定义数据更新的接口方法updateUser
/**
* 更新user对象
* @param user
*/
public void updateUser(User user);
配置文件中配置对应的sql
<update id="updateUser" parameterType="cn.offcn.entity.User">
update user set name=#{name},gender=#{gender},age=#{age},birthday=#{birthday} where id=#{id}
</update>
添加测试方法
@Test
public void testUpdateUser() throws Exception{
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
//创建User对象
User user=new User("何晓飞","女",21,new Date());
//指定要更新id
user.setId(1);
//调用updateUser方法进行修改
userDao.updateUser(user);
//关闭连接
MyBatisUtils.close(session);
}
数据删除
定义数据删除的接口方法deleteUser
/**
* 根据id删除指定user
* @param id
*/
public void deleteUser(Integer id);
配置文件中配置对应的sql
<delete id="deleteUser" parameterType="int">
delete from user where id=#{id}
</delete>
添加测试方法
@Test
public void testDeleteUser() throws Exception{
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
//调用deleteUser方法进行删除
userDao.deleteUser(2);
//关闭连接
MyBatisUtils.close(session);
}
#{}和${}的区别
1.#{}是预编译处理,${}是字符串替换。
2.Mybatis在处理${}时,就是把${}替换成变量的值。
3.Mybatis在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值。
4.使用#{}可以有效的防止 SQL 注入,提高系统安全性。
MyBatis实现模糊查询
模糊查询在我们开发中是一项必不可缺少的重要内容。对于我们mybatis实现模糊查询有三种方式,以下具体的实现步聚:
添加模糊查询的接口方法likeSearchUsers
/**
* 根据name模糊查询
* @param name
* @return
*/
public List<User> likeSearcherUsers(String name);
配置接口方法对应的sql文件
配置占位符方式#
<select id="likeSearcherUsers" parameterType="String" resultType="cn.offcn.entity.User">
select * from user where name like ’%‘ #{name} ’%‘
</select>
配置拼接字符串方式$
<select id="likeSearcherUsers" parameterType="String" resultType="cn.offcn.entity.User">
select * from user where name like '%${value}%'
</select>
我们在上面将原来的#{}占位符,改成了${value}
。注意如果用模糊查询的这种写法,那么${value}
的写法就是固定的,不能写成其它名字。
配置mysql函数方式concat
<select id="likeSearcherUsers" parameterType="String" resultType="cn.offcn.entity.User">
select * from user where name like concat('%',#{name},'%')
</select>
模糊查询测试
@Test
public void testSearcherUser() throws Exception{
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
//调用likeSearcherUsersr方法进行模糊查询
List<User> userList = userDao.likeSearcherUsers("张");
//遍历输出
userList.forEach(System.out::print);
//关闭连接
MyBatisUtils.close(session);
}
MyBatis的参数处理
parameterType 配置参数
参数的使用说明
上一章节中已经介绍了SQL语句传参,使用标签的 parameterType 属性来设定。该属性的取值可以是基本类型,引用类型(例如:String 类型),还可以是实体类类型(POJO 类)。同时也可以使用实体类的包装类,本章节将介绍如何使用实体类的包装类作为参数传递。
参数配置的注意事项
- 基本类型和String可以直接写类型名称也可以使用包名.类名的方式,例如:java.lang.String。
- 实体类类型,目前我们只能使用全限定类名。
- 究其原因,是mybaits在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。在今天课程的最后一个章节中将讲解如何注册实体类的别名。
- mybatis 的官方文档的说明请参考下面表格数据。
别名 | 映射的类型 |
---|---|
_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 |
这些都是支持的默认别名。我们也可以从源码角度来看它们分别都是如何定义出来的。可以参考 TypeAliasRegistery.class 的源码
传递 pojo 包装对象
开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数,Pojo 类中包含 pojo。 需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。
编写QueryVo
package cn.offcn.entity;
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
编写持久层接口UserMapper
public interface UserMapper {
public List<User> getUserByLikeName(QueryVo queryVo);
}
配置接口方法对应的sql文件
<select id="getUserByLikeName" parameterType="cn.offcn.entity.QueryVo" resultType="cn.offcn.entity.User">
select * from user where name like #{user.name}
</select>
测试QueryVo对象作为参数
@Test
public void testGetUserByLikeName() throws Exception{
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//创建User对象
User user=new User();
user.setName("%李%");
QueryVo queryVo=new QueryVo();
queryVo.setUser(user);
//调用deleteUser方法进行模糊查询
List<User> userList = userMapper.getUserByLikeName(queryVo);
userList.forEach(System.out::print);
//关闭连接
MyBatisUtils.close(session);
}
map集合数据作为参数的处理方式
添加接口方法参数使用map集合
public List<User> getUserByGenderAndAge(Map<String,Object> map);
配置接口对应的sql配置
<!--#{}中参数必须和Map集合中的key保存一致,表示取Map集合中指定key的值。-->
<select id="getUserByGenderAndAge" parameterType="java.util.Map" resultType="cn.offcn.entity.User">
select * from user where gender=#{sex} and age=#{age}
</select>
测试map集合作为参数
@Test
public void testGetUserByGenderAndAge() throws Exception{
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
Map<String,Object> map=new HashMap<String,Object>();
map.put("sex","男");
map.put("age",22);
List<User> userList = userMapper.getUserByGenderAndAge(map);
userList.forEach(System.out::print);
//关闭连接
MyBatisUtils.close(session);
}
@Param方式解决多参数处理
@Param注解的介绍
@Param
注解用于给方法内的参数取别名,当方法中拥有多个参数时,我们无法一次性将这些参数进行传递,尤其多个参数具有不同的数据类型时无法传递,所以我们利用@Param
给每个方法中的参数取一个别名,在映射文件中使用别名进行取值。
添加接口方法参数使用map集合
public List<User> getUserByGenderAndBirthday(@Param("gen") String gender,Param("birth") Date birthday);
配置接口对应的sql配置
<select id="getUserByGenderAndBirthday" resultType="cn.offcn.entity.User">
select * from user where gender=#{gen} and age=#{birth}
</select>
测试注解方法
@Test
public void testGetUserByGenderAndBithday() throws Exception{
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR,1997);
calendar.set(Calendar.DAY_OF_MONTH,10);
calendar.set(Calendar.DAY_OF_MONTH,12);
Date birthday=calendar.getTime();
List<User> userList = userMapper.getUserByGenderAndBirthday("女",birthday);
userList.forEach(System.out::print);
//关闭连接
MyBatisUtils.close(session);
}
MyBatis查询结果封装
resultType结果类型
resultType属性介绍
resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。我们在前面的 CRUD 案例中已经对此属性进行过应用了。需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。例如:我们的实体类此时必须是全限定类名(今天最后一个章节会讲解如何配置实体类的别名)同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。
resultType属性的使用
基本类型
- 编写dao接口方法getTotalRecords参数是基本类型
/**
* 统计所有记录数
* @return
*/
public int getTotalRecords();
- 配置接口方法对应的sql语句
<select id="getTotalRecords" resultType="int">
select count(*) from user
</select>
- 测试查询结果
@Test
public void testGetTotalRecords() throws Exception{
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用getTotalRecords统计记录数
int totalRecords= userMapper.getTotalRecords();
//打印totalRecords
System.out.println("总记录数:"+totalRecords);
//关闭连接
MyBatisUtils.close(session);
}
实体类型
- 编写dao接口方法getAllInfo参数是对象类型
public List<User> getUsers();
- 配置接口方法对应的sql语句
<select id="getUsers" resultType="cn.offcn.entity.User">
select * from user
</select>
- 测试查询结果
@Test
public void testGetUser() throws Exception{
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用getUsers查询所有记录
List<User> userList = userMapper.getUsers();
//遍历结果
userList.forEach(System.out::print);
//关闭连接
MyBatisUtils.close(session);
}
特殊情况
如果个修改了实体类User中的id属性值,比如修改成了userId,此时查询出的结果没有把表中的id值映射到userId属性中,因为属性和表中的列名不一致,内部无法用反射技术进行映射,所以为空。
public class User implements Serializable {
private Integer userId;
private String userName;
private String gender;
private Integer age;
private Date userBirthday;
public User() {}
public User(String userName, String gender, Integer age, Date userBirthday) {
this.userName = userName;
this.gender = gender;
this.age = age;
this.birthday = userBirthday;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
//生成其它属性的getter和setter方法
}
@Test
public void testGetUser() throws Exception{
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//调用getUsers查询所有记录
List<User> userList = userMapper.getUsers();
//遍历结果
userList.forEach(System.out::print);
//关闭连接
MyBatisUtils.close(session);
}
经过上述测试结果发现userId属性为null,没有被赋上值。
解决方案:修改映射配置,采用别名设置,让结果集中的列与实体类中的属性对应。
<select id="getUsers" resultType="cn.offcn.entity.User">
select id userId,name userName,gender gender,age age,birthday userBirthday from user
</select>
resultMap自定义结果类型
resultMap标签介绍
resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。
定义接口方法getUserById
/**
* 根据id查询指定User对象
* @param id
* @return
*/
public User getUserById(int id);
在sql的配置文件中定义resultMap
<select id="getUserById" parameterType="int" resultMap="UserResultMap">
select * from user where userId=#{id}
</select>
<resultMap id="UserResultMap" type="cn.offcn.entity.User">
</resultMap>
此处我们使用resultMap而不是resultType, resultType是直接写结果类型,resultMap是映射结果集与类中属性对应关系。
resultMap标签中的id表示一个唯一标记是resultMap的名称。
type: 表示该resultMap返回的类型。
使用定义的resultMap配置查询的sql语句
<resultMap id="UserResultMap" type="cn.offcn.entity.User">
<id column="id" property="userId"></id>
<result column="name" property="userName"></result>
<result column="gender" property="gender"></result>
<result column="age" property="age"></result>
<result column="birthday" property="userBirthday"></result>
</resultMap>
id 标签:用于指定主键字段
result 标签:用于指定非主键字段
column 属性:用于指定数据库列名
property 属性:用于指定实体类属性名称
测试查询结果
@Test
public void testGetUserById() throws Exception{
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//查询user
User user = userMapper.getUserById(1);
//打印user
System.out.println(user);
//关闭连接
MyBatisUtils.close(session);
}
SqlMapConfig.xml配置文件内容解析
在主配置SqlMapConfig.xml中,定义了很多标签,我们现在只是使用了一部分标签 ,主配置文件中可以出现的标签 用dtd文 件进行约束。下面介绍其它标签的使用含义。
标签的配置规范,查看dtd规范文件
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright 2009-2016 the original author or authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
<!ELEMENT databaseIdProvider (property*)>
<!ATTLIST databaseIdProvider
type CDATA #REQUIRED
>
<!ELEMENT properties (property*)>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>
<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT settings (setting+)>
<!ELEMENT setting EMPTY>
<!ATTLIST setting
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT typeAliases (typeAlias*,package*)>
<!ELEMENT typeAlias EMPTY>
<!ATTLIST typeAlias
type CDATA #REQUIRED
alias CDATA #IMPLIED
>
<!ELEMENT typeHandlers (typeHandler*,package*)>
<!ELEMENT typeHandler EMPTY>
<!ATTLIST typeHandler
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
handler CDATA #REQUIRED
>
<!ELEMENT objectFactory (property*)>
<!ATTLIST objectFactory
type CDATA #REQUIRED
>
<!ELEMENT objectWrapperFactory EMPTY>
<!ATTLIST objectWrapperFactory
type CDATA #REQUIRED
>
<!ELEMENT reflectorFactory EMPTY>
<!ATTLIST reflectorFactory
type CDATA #REQUIRED
>
<!ELEMENT plugins (plugin+)>
<!ELEMENT plugin (property*)>
<!ATTLIST plugin
interceptor CDATA #REQUIRED
>
<!ELEMENT environments (environment+)>
<!ATTLIST environments
default CDATA #REQUIRED
>
<!ELEMENT environment (transactionManager,dataSource)>
<!ATTLIST environment
id CDATA #REQUIRED
>
<!ELEMENT transactionManager (property*)>
<!ATTLIST transactionManager
type CDATA #REQUIRED
>
<!ELEMENT dataSource (property*)>
<!ATTLIST dataSource
type CDATA #REQUIRED
>
<!ELEMENT mappers (mapper*,package*)>
<!ELEMENT mapper EMPTY>
<!ATTLIST mapper
resource CDATA #IMPLIED
url CDATA #IMPLIED
class CDATA #IMPLIED
>
<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>
properties标签详解
在使用 properties 标签配置时,在xml中可以引用properties属性文件中key的值,日后修改properties属性文 件中key的值时,不用修改xml文件,从而提高了效率。
新建一个dbconfig.properties属性文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///mybatis001
jdbc.username=root
jdbc.password=root
在SqlMapperConfig.xml中可以引用属性文件中key的值
<properties resource="dbconfig.properties"></properties>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
typeAliases标签详解
在前面我们讲的 Mybatis 支持的默认别名,我们也可以采用自定义别名方式来开发。
在SqlMapperConfig.xml中定义别名
<typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="user" type="cn.offcn.entity.User"/>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
<package name="cn.offcn.entity"/>
<package name=" 其它包 "/>
</typeAliases>
在userMapper.xml中使用别名
<select id="getUsers" resultType="user">
select * from user
</select>
<select id="getUserById" parameterType="int" resultMap="UserResultMap">
select * from user where userId=#{id}
</select>
<resultMap id="UserResultMap" type="user">
<id column="id" property="userId"></id>
<result column="name" property="userName"></result>
<result column="gender" property="gender"></result>
<result column="age" property="age"></result>
<result column="birthday" property="userBirthday"></result>
</resultMap>
mappers映射器标签详解
mappers映射器用于指定映射文件的位置。
<!--使用相对于类路径的资源-->
<mappers>
<mapper resource="cn/offcn/dao/UserDao.xml"/>
</mappers>
<!--使用mapper接口类路径-->
如:<mapper class="cn.offcn.dao.UserDao"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
MyBatis自动返回主键值
返回主键值技术的业务应用场景
对于自增主键在某些业务中保存一个对象后,需要使用到这个主键完成后续的业务逻辑,比如:要保存订单后,还要保存订单项信息,订单项相信需要用到订单主键。所以应用场合很多,下面我们来应用一下返回主键值操作。
接口中编写addUser方法
/**
* 保存user对象
* @param user
*/
public void saveUser(User user);
在sql的配置文件中添加接口方法对应的sql配置
<select id="saveUser" parameterType="cn.offcn.entity.User">
insert into user (name,gender,age,birthday) values(#{name},#{gender},#{age},#{birthday})
</select>
在sql标签上添加返回自增长的主键值的配置
第一种方式:
<insert id="saveUser" parameterType="cn.offcn.entity.User" useGeneratedKeys="true" keyProperty="id">
insert into user (name,gender,age,birthday) values(#{name},#{gender},#{age},#{birthday})
</insert>
useGeneratedKeys: 表示开启获取自增主键值。
keyProperty: 表示从表中取到主键值后赋给User类中的哪个属性。
第二种方式:使用selectKey标签和mysql内置函数
<insert id="saveUser" parameterType="cn.offcn.entity.User">
<selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into user (name,gender,age,birthday) values(#{name},#{gender},#{age},#{birthday})
</insert>
keyColumn:指定取数据库中哪一列的值(通常指主键列)。
keyProperty: 表示取出主键值后赋值User对象的哪个属性。
resultType: 表示对象的属性类型
order:表示完后sql语句之前还是之后把主键值赋给实体类对应属性。
测试
@Test
public void testsaveUser() throws Exception{
//获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
//调用SqlSession 创建 UserDao接口的代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
//创建User对象
User user=new User("刘备","男",30,new Date());
//保存User对象
userMapper.saveUser(user);
//打印user的id属性值
System.out.println(user.getId());
//提交事务
session.commit();
//关闭连接
MyBatisUtils.close(session);
}