目录
一、什么是MyBatis
二、Mybatis相对JDBC有哪些优势
三、Mybatis框架的原理介绍
四、Mybatis全局配置文件
1.全局配置文件的类容和顺序
五、映射文件
1.输入映射parameterType
2.resultType结果映射
3.resultMap结果映射
六、Mybatis之传统Dao层的开发(该方式很少用)
1.开发环境准备:
2.需求分析:
3.代码实现
七、Mybatis之Mapper接口的开发方式
1.需求分析
2.Mapper开发代理规范
3.代码实现
八、Mybatis一级缓存
1.证明一级缓存的存在:
九、Mybatis二级缓存
1.开启二级缓存
2.禁用二级缓存
3.刷新二级缓存
小结:
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
相关视频教程在线观看:动力节点MyBatis教程实战精讲(适用于SSM框架初学者课程循序渐进,深入浅出)_哔哩哔哩_bilibili如果你所做的项目业务比较复杂,那么在DAO层可以考虑使用MyBatis框架,MyBatis本是apache的一个开源项目iBATIS,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。https://www.bilibili.com/video/BV185411s7Ry
首先我们来看一看jdbc连接数据库的连接方法:
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1、加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//2、通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "root");
//3、定义sql语句 ?表示占位符
String sql = "select * from user where username = ?";
//4、获取预处理statement
preparedStatement = connection.prepareStatement(sql);
//5、设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "王五");
//6、向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
//7、遍历查询结果集
while(resultSet.next()){
User user
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//8、释放资源
if(resultSet!=null){
try {
resultSet.close();//释放结果集
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();//关闭数据库连接
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
通过上面的一段jdbc连接数据代码,我们看有哪些不好的地方:
1.在创建connection的时候,存在硬编码问题(也就是直接把连接信息写死,不方便后期维护)
2.preparedStatement对象在执行sql语句的时候存在硬编码问题。
3.每次在进行一次数据库连接后都会关闭数据库连接,频繁的开启/关闭数据连接影响性能。
简单的说一下mybatis相对jdbc的优势:
1.mybatis是把连接数据库的信息都是写在配置文件中,因此不存在硬编码问题,方便后期维护。
2.mybatis执行的sql语句都是通过配置文件进行配置,不需要写在java代码中。
3.mybatis的连接池管理、缓存管理等让连接数据库和查询数据效率更高。
这里就通过一张图来对mybatis框架原理进行介绍吧:
SqlMapConfig.xml是Mybatis的全局配置文件,它的名称可以是任意,但是一般命名都为(SqlMapConfig)
2.常见配置详解
properties标签:
Mybatis可以通过该标签来读取java配置信息:
例如在工程中对数据源信息写在db.properties文件中,可以通过properties标签来加载该文件。
db.properties:
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/mybatis
db.username=root
db.password=12345678
SqlMapConfig.xml使用properties标签:
因此在property中的name属性的值和value比properties中的resource属性先加载。后加载的db.properties会覆盖于property加载属性和值。
settings标签:
该标签时mybatis的全局设置,该设置会影响mybatis的运行。
一般我们使用使用该标签来开启二级缓存和懒加载。
typeAliases标签
该标签是对po类进行别名设置,这样,在后面使用po类的时候就可以直接通过别名引用,而不需要通过po类的全限定名来引用。这样可以提高我们的开发效率。
首先介绍下Mybatis的默认提供的别名有
自定义单个别名:这种方式只能定义单个类的别名。
下面的代码就是把com.lc.mybatis.po.User类定义为user的别名
自定义之批量定义别名:
下面代码是把com.lc.mybatis.po类下的所有类都声明别名,默认的别名就是类名(类名大小写都可以)
mappers标签
该标签的作用是加载映射文件
方式一:
该方式是加载相对于类路径下的映射文件:
方式二:
该方式使用全限定路径
方式三:
该方式使用mapper接口的全限定类名
此方式要求:
Mapper接口Mapper映射文件名称相同且在同一个目录下。
方式四:
该方式是加载指定包下的所有映射文件
此方式要求:
Mapper接口Mapper映射文件名称相同且在同一个目录下。
第一种:简单类型
#{}表示占位符?,parameterType接收简单类型的参数时,里面的名称可以任意
${}表示拼接符,parameterType接收简单类型的参数时,里面的名称必须是value
第二种:pojo类型
这里通过用户的用户名进行模糊查询演示pojo类型
在映射文件中添加模糊查询语句:
user类
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
测试类
public class UserDao{
//根据用户名进行模糊查询
@Override
public List findUserByPojo(){
//全局配置文件路径:
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUsetname("张三");
List users = (List)sqlSession.selectList("test.findUsersByPojo",user);//传入pojo
System.out.println(users);
sqlSession.close();
return users;
}
}
第三种:包装类型pojo
这里通过用户名和用户地址对用户进行查询来演示包装类型pojo:
首先创建包装pojo类:
public class UserVO {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
在映射文件中添加查询语句:
测试类
public class UserDao{
//根据用户名和地址进行查询
@Override
public List findUserByPojo1(){
//全局配置文件路径:
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUsetname("张三");
user.setAddress("郝汉山");
UserVO userVo = new UserVO();
userVo.setUser(user);
List users = (List)sqlSession.selectList("test.findUsersByPojo1",userVo);//传入pojo包装类
System.out.println(users);
sqlSession.close();
return users;
}
}
第四种:map集合类型
这里通过查询用户信息演示:
在映射文件中添加该查询语句:
测试方法
public class UserDao{
//根据用户名和地址进行查询
@Override
public List findUserByMap(){
//全局配置文件路径:
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Map map = new HashMap<>();
map.put("username","张三");
map.put("address","郝汉山");
List users = (List) sqlSession.selectList("test.findUsersByMap",map);//传入pojo包装类
System.out.println(users);
sqlSession.close();
return users;
}
}
resultType结果映射要求:需要查询结果的列名和映射的对象的属性名一致,这样才能映射成功。如果映射没成功也不会报错,只是映射结果中对象的相应属性没有值,为空。如果映射的列名和对象中的属性名全部不一致,那么映射的对象为空。如果在使用sql语句查询的时候给查询结果列设置了别名,则别名要和映射结果对象的属性名一致,这样才能保证映射成功。
第一种:简单类型
注意: 如果结果映射为简单类型,则需要查询的结果为一列才能映射成功。
例如:查询用户表中用户的总数。
映射文件为:
测试代码:
public class UserDao{
@Override
public int findUserCount(){
//全局配置文件路径:
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
int userCount= sqlSession.selectOne("test.findUserCount");
System.out.println(userCount);
sqlSession.close();
return userCount;
}
}
第二种:pojo结果映射
这里操作pojo输入映射。
使用resultMap结果映射时,不需要查询出来的结果集的列名和映射结果对象的属性名相同,但是需要声明一个resultMap,手动的方式来对列名和对象属性进行映射。(resultMap一般用于多表关联映射)
例如:通过查询用户表中的用户,并对查询出来的用户表的列名设置别名。
映射文件添加查询语句:
[id]:定义resultMap的唯一标识
[type]:定义该resultMap最终映射的pojo对象
[id标签]:映射结果集的唯一标识列,如果是多个字段联合唯一,则定义多个id标签
[result标签]:映射结果集的普通列
[column]:SQL查询的列名,如果列有别名,则该处填写别名
[property]:pojo对象的属性名
测试类:
public class UserDao{
@Override
public User findUserResultMap(){
//全局配置文件路径:
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("test.findUserResultMap",1);
System.out.println(user);
sqlSession.close();
return user;
}
}
4.动态sql
在mybatis中提供了一些动态sql标签,可以让我们开发效率更快,这些动态sql语句可以增加我们写的sql语句的重用性,常用的动态sql语句标签有:if标签、sql片段(需要先定义后使用)、where标签、foreach标签
通过下面例子来演示动态sql常用的标签:
需求分析:查询用户表中相关的用户。
发出相关的sq语句有:select * from user where username like "%张三%" and address= ?;
select * from user;
select * from user where id in(1,2,10);
如何才能让这些语句在我们需要的时候就使用,不需要的时候就不适用。其实我们发现这三条语句有很多重复的地方,我们如果能做到让重复的能够重复使用,没有重复的可以按我们需求使用的话就能减少我们写很多sql语句。当然动态sql就是帮我们解决这些问题的。
映射文件:
AND username LIKE '%${user.username}%'
AND address=#{user.address}
AND id IN
#{item}
userVo类:
public class UserVO {
private User user;
private List idList;
public List getIdList() {
return idList;
}
public void setIdList(List idList) {
this.idList = idList;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
测试类:
public class UserDao{
@Override
public void findUserList(){
//全局配置文件路径:
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
//测试foreach语句执行
UserVO userVo = new UserVO();
List idList = new ArrayList<>();
idList.add(1);
idList.add(2);
idList.add(3);
userVo.setIdList(idList);
//测试select * from user where username like ? and address=?;
//User user = new User();
//user.setUsername("张三");
//user.setAddress("武当山");
//userVo.setUser(user);
List users = sqlSession.selectOne("test.findUserList",userVo);
System.out.println(users);
sqlSession.close();
}
}
根据ID查询查询用户、根据用户名称进行模糊查询、添加用户
创建用户表对应pojo类User:
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//setter和get方法省略
}
创建mybatis的全局配置文件SqlMapConfig.xml:用于配置数据源、事务、映射文件加载等信息
创建需求开发的映射文件User.xml配置文件:并在全局配置文件中添加该映射文件
SELECT LAST_INSERT_ID()
INSERT INTO USER(username,sex,birthday,address) VALUES(#{username},#{sex},#{birthday},#{address})
dao层接口:
public interface UserDao {
//根据用户id查询用户
public User findUserById(int id);
//根据用户名进行模糊查询
public List findUsersByName(String name);
//添加用户
public void addUser(User user);
}
dao层的实现:
public class UserDaoImpl implements UserDao{
//通过构造方法注入sqlSessionFactory
private SqlSessionFactory sqlSessionFactory;
public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
//根据id查询用户
@Override
public User findUserById(int id) {
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("test.findUserById", id);
System.out.println(user);
sqlSession.close();
return user;
}
//根据用户名进行模糊查询
@Override
public List findUsersByName(String name){
SqlSession sqlSession = sqlSessionFactory.openSession();
List users = sqlSession.selectList("test.findUsersByName",name);
System.out.println(users);
sqlSession.close();
return users;
}
//添加用户
public List addUser(String name){
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUsername("超哥2");
user.setAddress("成都市");
//调用SqlSession的增删改查方法
//第一个参数:表示statement的唯一表示
//第一个参数:表示占位符的
sqlSession.insert("test.insertUser", user);
System.out.println(user.getId());
//切记:增删改操作需要提交事务。
sqlSession.commit();
//关闭资源
sqlSession.close();
}
测试类:这里就只通过根据id查找用户进行测试
public class UserDaoTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserById() {
UserDao userDao = new UserDaoImpl(sqlSessionFactory);
userDao.findUserById(1);
}
}
该方式开发,不需要写dao层的实现类,而是mybatis根据映射文件等信息对接口进行jdk动态代理生成代理类来实现接口中的方法,因此,采用这种方式,我们只需要编辑接口,而不需要去写实现。
根据id查询用户。
准备po类:
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//getter和setter方法省略
}
Mapper接口:
public interface UserMapper {
public User findUserById(int id);
}
UserMapper.xml配置文件:
在全局配置文件SqlMapperConfig中添加该映射文件
测试代码:
public class UserMapperTest {
private SqlSessionFactory sqlSessionFactory ;
@Before
public void setUp() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserById() {
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取UserMapper的代理类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findUserById(10);
System.out.println(user);
sqlSession.close();
}
}
mybatis提供查询缓存,如果缓存中有数据,就不用从数据库中获取,用于减轻数据压力,提高系统性能
一级缓存是sqlSession级别的缓存,在操作数据库的时候,需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的SqlSession的缓存区域(HashMap)是互相不受影响的。
mybatis默认是支持一级缓存的。
验证方法:
public void testOneLevelCache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取UserMapper的代理类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//第一次查询
User user = userMapper.findUserById(10);
System.out.println(user);
//第二次查询
User user2 = userMapper.findUserById(10);
System.out.println(user2);
sqlSes
通过执行该方法,查看打印日志可以看出就执行了一次sql语句的查询,因此可以判断第二次查询是没有从数据库中进行查询的。
那当什么时候一级缓存会被清空呢?
通过测试发现,当在第二次查询数据库之前对数据库进行了写的操作后,第二次查询就不会从缓存中查询结果,而是直接从数据中查询结果。另一种就是在第一查询结果后,手动的方式清空一级缓存(使用sqlSession.clearCache();方法。)
测试方法:
二级缓存是Mapper级别的缓存。多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是夸SqlSession的。(二级缓存Mybatis默认是关闭的,需要自己去手动配置开启或可以自己选择用哪个厂家的缓存来作为二级缓存)
首先在全局配置文件中配置开启二级缓存功能:
在映射文件中去选择是否该映射文件使用二级缓存:
如果使用就进行以下配置,如果不是用,就不需要对映射文件做任何修改。
也可以设置选择二级缓存提工商
以下是我选择使用EhcacheCache缓存(注意:在使用EhcacheCache缓存需要导入jar包)
验证二级缓存是否开启:
验证方法如下:
@Test
//测试二级缓存是否开启
public void testTwoLevelCache() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
//获取UserMapper的代理类
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
User user1 = userMapper1.findUserById(10);
System.out.println(user1);
//当sqlSession1执行close()的时候,才把sqlSession1查询的结果写到二级缓存中去。
sqlSession1.close();
// User u = new User();
// u.setUsername("小胖");
// u.setAddress("成都");
// u.setSex("2");
// u.setBirthday(new Date());
// userMapper3.insertUser(u);
// //当其中任何一个sqlSession执行commit()方法后将刷新整个缓存区
// sqlSession3.commit();
// sqlSession3.close();
//第二次查询
User user2 = userMapper2.findUserById(10);
System.out.println(user2);
sqlSession2.close();
}
测试结果:
同时通过测试,当在第二次查询的时候,向数据库提交数据后,就会对缓存进行刷新,这样在第二次查询的时候就没有走缓存,而是走的数据库。
由于二级缓存默认是关闭的,如果不开启就不会使用二级缓存。如果,我们开启了二级缓存,而在有些查询结果集中,不需要受到二级缓存影响,该怎么去做呢?
在select查询中,默认是使用了二级缓存,如果不想使用二级缓存,就在select标签中有一个useCache的属性设置为false,就代表不使用二级缓存,每次进行查询数据都不会从缓存总获取,而是直接从数据库中进行查询。useCache的默认值是true,即代表statement使用二级缓存。
在statement中设置flushCache=true,可以刷新二级缓存。默认情况下,select语句中的flushCache是false。如果是insert、update、delete语句,那么flushCache的默认值是true。如果将select语句中的flushCache值改为true,就意味着查询语句的二级缓存失效,每次查询都会从数据库进行查询。如果将select语句的flushCache值为false,就代表该查询语句使用了二级缓存,如果在数据库中修改了数据,而二级缓存中的数据还是原来的数据,那么这样就会出现脏读。
flushCache设置如下: