本篇来学习MyBatis的入门使用。
准备工作
- 导入数据库表和数据
- 数据库名为eesy_mybatis。
- account表,用户账户表(账户表和用户表时多对一关系)
- user表,用户表
- role表,角色表
- user_role,用户和角色中间表(用户和角色是多对多关系)
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL auto_increment,
`username` varchar(32) NOT NULL COMMENT '用户名称',
`birthday` datetime default NULL COMMENT '生日',
`sex` char(1) default NULL COMMENT '性别',
`address` varchar(256) default NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (41,'老王','2018-02-27 17:47:08','男','北京'),(42,'小二王','2018-03-02 15:09:37','女','北京金燕龙'),(43,'小二王','2018-03-04 11:34:34','女','北京金燕龙'),(45,'传智播客','2018-03-04 12:04:06','男','北京金燕龙'),(46,'老王','2018-03-07 17:37:26','男','北京'),(48,'小马宝莉','2018-03-08 11:44:00','女','北京修正');
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`ID` int(11) NOT NULL COMMENT '编号',
`UID` int(11) default NULL COMMENT '用户编号',
`MONEY` double default NULL COMMENT '金额',
PRIMARY KEY (`ID`),
KEY `FK_Reference_8` (`UID`),
CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `account`(`ID`,`UID`,`MONEY`) values (1,41,1000),(2,45,1000),(3,41,2000);
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`ID` int(11) NOT NULL COMMENT '编号',
`ROLE_NAME` varchar(30) default NULL COMMENT '角色名称',
`ROLE_DESC` varchar(60) default NULL COMMENT '角色描述',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `role`(`ID`,`ROLE_NAME`,`ROLE_DESC`) values (1,'院长','管理整个学院'),(2,'总裁','管理整个公司'),(3,'校长','管理整个学校');
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`UID` int(11) NOT NULL COMMENT '用户编号',
`RID` int(11) NOT NULL COMMENT '角色编号',
PRIMARY KEY (`UID`,`RID`),
KEY `FK_Reference_10` (`RID`),
CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`),
CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user_role`(`UID`,`RID`) values (41,1),(45,1),(41,2);
- 导入依赖
- mybatis:3.4.5
- mysql驱动:5.1.6
- log4j:1.2.12
- junit:4.10
org.mybatis
mybatis
3.4.5
mysql
mysql-connector-java
5.1.6
runtime
log4j
log4j
1.2.12
junit
junit
4.10
test
- 导入log4j配置文件
resources目录下,创建log4j配置文件,log4j.properties
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
- 创建数据库配置文件
resources目录下,创建数据库配置文件,jdbcConfig.properties
数据库,我们使用mysql,库名为:eesy_mybatis
用户名和密码使用大家自己的
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy_mybatis
jdbc.username=root
jdbc.password=hezihao123
- 创建MyBatis配置文件
resources目录下,创建MyBatis配置文件,SqlMapConfig.xml
- MyBatis的所有配置,都放在configuration标签内
- properties标签,引入刚才的mysql配置,jdbcConfig.properties
- typeAliases标签,配置模型别名,可以使用typeAlias标签单独指定,但一般我们会使用package标签,指定统一的一个包
- environments标签,配置数据库环境,标签内部可以指定多个数据源,default属性指定要使用的,这里我们就一个mysql
- transactionManager标签,指定事务类型,我们使用MyBatis封装了JDBC,所以我们也是使用JDBC
- dataSource标签,指定数据源,也是指定连接池,POOLED为MyBatis内置的连接池,UNPOOLED为不使用连接池,一般我们都会使用POOLED
- mappers标签,resource属性指定mapper.xml文件路径,resource属性指定注解类的类名。一般不用,需要一个个指定。一般使用package标签,指定一个包。
- package标签,在mappers标签内指定mapper.xml文件路径和注解类的包名。
账户实体Account
/**
* 账户实体
*/
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
//省略get、set和toString方法
}
- 建立Java的Dao层包,例如我的项目是com.itheima.dao。
- 在resources目录下,建立和Dao包名相同目录。(要一个个建立!)
- 账户表的单表操作
/**
* 账户Dao层接口
*/
public interface IAccountDao {
/**
* 查询所有账户
*/
List findAll();
/**
* 根据用户Id查询账户
*/
List findAccountByUid(Integer id);
}
- 创建Mapper文件:IAccountDao.xml,名字要和接口名一致
- mapper标签,持久层操作方法,都必须写在里面
- namespace属性,命名空间,填入Dao层的全限定类名(重要!这是mapper中的方法的唯一标识的前缀,每个方法时由命名空间+方法名,来标识的)
- select标签,查询标签,id为Dao层声明的接口方法名,必须要一样!
- resultType,方法返回值的映射类型,可以使用别名或全限定类名,别名就是SqlMapConfig.xml中typeAliases标签配置的
- parameterType,当接口方法有一个参数时,需要指定入参的java类型,同样可以使用别名和类的全限定类名
- 获取接口参数,使用#{}占位符的方式,名字为接口参数的名字
- 测试
- Resources资源类,调用getResourceAsStream()方法,加载配置文件
- SqlSessionFactory,SqlSession工厂类,专门来生产SqlSession对象,它需要用SqlSessionFactoryBuilder来构建
- 调用SqlSessionFactory的openSession(),获取一个SqlSession
- 调用SqlSession的getMapper()方法,传入Dao层接口类的Class,动态代理生成接口的实现类
注意点:
- openSession()时,默认事务是设置为手动提交的,如果传入true,则代表开启自动提交事务,一般我们都是手动控制
- 操作完,如果是插入、更新等操作,需要调用SqlSession的commit()方法提交事务。
- 使用完,关闭SqlSession和配置文件的输入流
public class AccountTest {
private InputStream inputStream;
private SqlSession session;
private IAccountDao accountDao;
@Before
public void init() throws IOException {
//1.读取配置文件
inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder()
.build(inputStream);
//3.使用工厂生产SqlSession对象,autoCommit设置为true时,为自动提交事务,默认为false,一般我们都手动控制事务
// session = factory.openSession(true);
session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
accountDao = session.getMapper(IAccountDao.class);
}
@After
public void destroy() throws IOException {
//注意,如果openSession(),没有传autoCommit参数,或者设置为false,则需要自己手动提交事务
session.commit();
//6.释放资源
session.close();
inputStream.close();
}
/**
* 测试查询所有账户
*/
@Test
public void testFindAll() {
List resultList = accountDao.findAll();
for (Account account : resultList) {
System.out.println(account);
}
}
}
用户表的增删查改
创建实体
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//省略get、set和toString方法
}
- 创建Dao层接口
/**
* 用户表持久层接口
*/
public interface IUserDao {
/**
* 查询所有用户,同时获取到用户下的所有账户信息
*/
List findAll();
/**
* 保存用户
*/
int saveUser(User user);
/**
* 更新用户
*/
void updateUser(User user);
/**
* 删除用户
*
* @param userId 用户Id
*/
void deleteUser(int userId);
/**
* 按用户Id查询用户信息
*/
User findById(int userId);
}
- 创建Mapper文件:IUserDao.xml
- 增:insert标签
- 删:delete标签
- 改:update标签
select last_insert_id();
insert into user(username, address, sex, birthday) values(#{username}, #{address}, #{sex}, #{birthday});
update user set username = #{username}, address = #{address},sex = #{sex}, birthday = #{birthday} where id = #{id};
select * from user
where id = #{uid};
模糊查询
- Dao接口中添加方法
public interface IUserDao {
//...省略其他方法
/**
* 根据名称,模糊查询用户信息
*/
User findByName(int username);
}
- Mapper的XML文件中添加方法
有2种方式:
-
#{}
占位符方式,以预编译、占位符方式嵌入参数,能防止SQL注入(常用) -
${}
字符串拼接方式,有一个默认叫value的值,只能适用于单个参数,不能防止SQL注入,限制大,很少用
查询总数量
- Dao层接口
public interface IUserDao {
//...省略其他方法
/**
* 查询总用户数
*/
int findTotal();
}
- Mapper的XML文件中添加方法
使用多层封装的JavaBean作为接口参数
一般我们结构很复杂的时候,会好几层模型的封装,MyBatis同样可以解析,XML中使用:变量名.属性名,这种方式访问变量的属性,多层的话,多个.就可以了
- 实体类
public class QueryVO {
private User user;
//省略get、set和toString方法
}
- Dao层接口
public interface IUserDao {
//...省略其他方法
/**
* 根据QueryVO查询用户信息
*/
List findUserByVo(QueryVO vo);
}
- Mapper的XML文件中添加方法
多个条件组合判断查询
需求:按用户的名称或性别来查询,条件不一定都有
- Dao层接口
public interface IUserDao {
//...省略其他方法
/**
* 根据条件查询用户信息
*
* @param user 查询条件
*/
List findUserByCondition(User user);
}
- Mapper的XML文件中添加方法
- where标签,表示where条件
- if标签,表示每个查询条件,test属性为添加条件时的前置条件
查询多个id的用户
修改QueryVO,增加ids属性。SQL查询使用in语句。
public class QueryVO {
private User user;
/**
* 要查询多个人的id
*/
private List ids;
//省略get、set、toString方法
}
- Dao层接口
public interface IUserDao {
//...省略其他方法
/**
* 根据QueryVO中的id集合,查询多个用户信息
*/
List findUserInIds(QueryVO vo);
}
- Mapper的XML文件中添加方法
- foreach标签,表示需要循环遍历
- collection属性:指定集合的变量名称
- open属性,遍历开始前的前缀
- close属性,遍历结束后的后缀
- separator属性,每次循环,每个条目之间的分隔符,一般用逗号
- item属性,每次循环,每个条目的名称
JavaBean和表字段不对应
返回结果我们可以使用resultType来指定返回JavaBean,但是如果JavaBean和表的字段名不一致,那么就要使用resultMap
- Mapper的XML文件
多表联查
多表联查,MyBatis提供了ResultMap,可以轻松配置一对多、多对一、多对多关系。
而MyBatis将多对一看做一对一,多对多看为一对多,提供以下2个标签,标签的属性,在后续实例中会讲解
- 一对多和多对多:使用collection标签
- 多对一:使用association标签
多对一的SQL联查
需求:查询所有账户,并查询出账户所属的用户信息(多个账户能被一个用户拥有,多对一关系)
- 修改账户实体,增加一个User对象
/**
* 账户实体
*/
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
/**
* 多对一关系映射,从表实体包含一个主表实体的引用
*/
private User user;
}
- IAccountDao增加方法
public interface IAccountDao {
/**
* 查询所有账户,同时包含用户的信息
*/
List findAllAccount();
}
- IAccountDao.xml
- association标签,一对一的关系映射
- property属性:表示封装User信息到Account的哪个属性上
- column属性:表示user和account关联的外键属性
- javaType属性:封装的数据类型
一对多的SQL联查
需求:查询用户表下所有用户,以及用户下的所有账户(一个用户下有多个账户,一对多关系)。
- 修改User实体,增加一个List
属性,表示当前用户拥有的所有角色
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
/**
* 一对多关系映射,主表实体应该包含从表实体的集合引用
*/
private List accounts;
//省略set、get和toString方法
}
- 修改IUserDao.java的findAll()方法,user表 left join account表
提供resultMap,userAccountMap
- collection属性:指定集合属性名称
- ofType属性:指定集合中的元素的类型
- property属性指定JavaBean中的字段属性
- column属性指定SQL查询中的字段名
多对多的SQL联查
需求:查询所有用户,以及用户的所有角色。(多对多关系,多个用户都可以拥有同一个角色,同时角色能被多个用户拥有)
- 角色实体
/**
* 角色实体
*/
public class Role implements Serializable {
private Integer roleId;
private String roleName;
private String roleDesc;
//省略set、get和toString
}
- IUserDao新增方法
public interface IUserDao {
/**
* 查询所有用户信息和角色信息
*/
List findUserRoles();
}
- IUserDao.xml
resultMap中配置和上面的一对多一样,也是使用collection属性,其他属性就不在赘述了。
需求:查询所有角色,并把拥有当前角色的所有用户都查询出来
- 修改角色实体,增加用户集合,表示拥有该角色的所有用户
/**
* 角色实体
*/
public class Role implements Serializable {
private Integer roleId;
private String roleName;
private String roleDesc;
/**
* 多对多关系映射,一个角色可以赋予给多个用户
*/
private List users;
//省略set、get和toString
}
- 增加IRoleDao,提供持久层接口
/**
* 角色表的Dao层接口
*/
public interface IRoleDao {
/**
* 查询所有角色
*/
List findAll();
}
- IRoleDao.xml
同样使用collection属性来配置用户列表。
注解方式
MyBatis除了提供XML方式编写Mapper外,还提供了注解方式,注解相比XML会少写一些代码,但是将SQL写到Java类中,一般不推荐。
- 基本CRUD,注解内写的SQL和XML配置是一样的,所以就不再赘述了
- @Select,查询
- @Insert,插入
- @Update,更新
- @Delete,删除
- ResultMap配置
- 如果实体和数据库字段不同,则可以使用@Results注解,使用@Results注解,配置映射关系
- @Results注解的id属性,为ResultMap的id,可以被其他方法引用。
- @Results注解的value属性,为@Result注解的数组,所以可以配置多个@Result注解
- @Result注解,id属性,布尔值,默认为false,代表是否是id属性,只有id属性,才会设置true
- @Result注解,column属性为数据库字段,property属性为实体字段
- 一对多关系配置
- @Result注解,many属性,存放@Many注解,用于配置一对多关系
- @Many注解,select属性,指定查询哪个Mapper(Dao层接口)的方法来查询,命名空间+方法名,例如这里需要调用IAccountDao的findAccountByUid()方法,值就为com.itheima.dao.IAccountDao.findAccountByUid。
- @Many注解,fetchType属性,指定查询时机,有3个值,LAZY(懒加载),EAGER(饥饿,马上查询),DEFAULT(默认值,就是马上查询)
关于懒加载,一般一对多时使用懒加载,而多对一选择马上加载
/**
* MyBatis注解练习
* 注解一共有4个:
* 1.@Select
* 2.@Inser
* 3.@Update
* 4.@Delete
*/
public interface IUserDao {
/**
* 查询所有
*/
@Select("select * from user")
//如果实体和数据库字段不同,则可以使用@Results注解,配置映射关系,column为数据库字段,property为实体字段
@Results(id = "userMap", value = {
@Result(
id = true,
column = "id",
property = "userId"
),
@Result(
column = "username",
property = "userName"
),
@Result(
column = "address",
property = "userAddress"
),
@Result(
column = "sex",
property = "userSex"
),
@Result(
column = "birthday",
property = "userBirthday"
),
//配置一对多的关联
@Result(
column = "id",
property = "accounts",
many = @Many(
select = "com.itheima.dao.IAccountDao.findAccountByUid",
fetchType = FetchType.LAZY
)
)
})
List findAll();
/**
* 保存用户
*/
@Insert("insert into user(username,address,sex,birthday) values(#{username},#{address},#{sex},#{birthday})")
@ResultMap(value = {"userMap"})
void saveUser(User user);
/**
* 更新用户
*/
@Update("update user set username=#{username}, address=#{address},sex=#{sex},birthday=#{birthday} where id = #{id}")
@ResultMap(value = {"userMap"})
void updateUser(User user);
/**
* 删除用户
*/
@Delete("delete from user where id = #{id}")
@ResultMap(value = {"userMap"})
void deleteUser(int id);
/**
* 根据用户Id,查询用户信息
*
* @param id 用户Id
*/
@Select("select * from user where id = #{id}")
@ResultMap(value = {"userMap"})
User findById(int id);
/**
* 根据名称模糊查询,用户列表
*
* @param username 用户名
*/
@Select("select * from user where username like #{username}")
@ResultMap(value = {"userMap"})
//或者使用字符串拼接的方式
// @Select("select * from user where username like '%${value}%'")
List findUserByName(String username);
/**
* 查询总数
*/
@Select("select count(*) from user")
@ResultMap(value = {"userMap"})
int findTotal();
}
- 多对一配置
和上面的一对多基本一样,有2个
- @Result注解,使用one属性,来配置一对一关系的关联
- @One注解,和@Many注解一样,也有select属性和fetchType属性,含义一样
public interface IAccountDao {
/**
* 查询所有账户,并且获取每个账户下的所属的用户信息
*/
@Select("select * from account")
@Results(id = "accountMap", value = {
@Result(
id = true,
column = "id",
property = "id"
),
@Result(
column = "uid",
property = "uid"
),
@Result(
column = "money",
property = "money"
),
//配置一对一的关联
@Result(
property = "user",
//column为调用User的单表查询时传的参数
column = "uid",
one = @One(
//select为去单表查询的方法
select = "com.itheima.dao.IUserDao.findById",
//多对一,使用立即加载
fetchType = FetchType.EAGER
)
)
})
List findAll();
/**
* 根据Id查询账户信息
*/
@Select("select * from account where uid = #{userId}")
Account findAccountByUid(int userId);
}
懒加载
MyBatis为了优化多表联查,提供了懒加载,当触发了实体的指定字段时,才触发查询,再将结果集放到最后的结果中。
原理,动态代理返回的模型类,模型类中需要懒加载的字段为null,拦截get方法,动态查询后再设置字段值,最后返回。
XML方式开发,懒加载配置
- MyBatis配置文件中开启懒加载支持
- 在Mapper.xml文件中,开启懒加载
- collection标签,增加fetchType属性,lazy为懒加载,eager为立即加载
注解方式开发,懒加载配置
其实上面讲解连表的时候,就提到了,@Many、@One注解都支持设置一个fetchType,设置为lazy则为懒加载
public interface IUserDao {
/**
* 查询所有
*/
@Select("select * from user")
//如果实体和数据库字段不同,则可以使用@Results注解,配置映射关系,column属性为数据库字段,property属性为实体字段
@Results(id = "userMap", value = {
@Result(
id = true,
column = "id",
property = "userId"
),
@Result(
column = "username",
property = "userName"
),
@Result(
column = "address",
property = "userAddress"
),
@Result(
column = "sex",
property = "userSex"
),
@Result(
column = "birthday",
property = "userBirthday"
),
//配置一对多的关联
@Result(
//column为调用AccountDao层方法传递的参数
column = "id",
//property为封装到实体的字段的名称
property = "accounts",
many = @Many(
select = "com.itheima.dao.IAccountDao.findAccountByUid",
fetchType = FetchType.LAZY
)
)
})
List findAll();
}
缓存
缓存等级
- 一级缓存(默认是开的),同一个SqlSession去获取的Mapper实例,查询方法会复用上次查询的结果对象和数据
- 二级缓存(默认是关的,需要手动开启),不同SqlSession,但同一个SqlSessionFactory,查询方法只复用查询的数据,对象不复用
注意:缓存在调用Mapper的update、insert、delete系列的方法时,会被清空掉,下次select查询会重新从数据库中查询,再缓存起来。
XML方式开始,开启二级缓存
- MyBatis配置文件中开启二级缓存支持,settings标签下,增加一个设置,cacheEnable为true
//...省略其他配置
- Mapper.xml中,做2步操作
- 增加一个cache标签,表示支持二级缓存
- 给支持二级缓存的方法的select标签,增加useCache属性,设置为true
注解方式开发,开启二级缓存
相比XML方法,直接在Mapper类上增加@CacheNamespace注解,设置blocking属性为true即可。
//支持二级缓存,blocking表示是否开启,默认为false,改为true,则开启
@CacheNamespace(blocking = true)
public interface IUserDao {
//...省略其他方法
}