一篇讲完MyBatis

MyBatis中文文档参考地址    http://www.mybatis.org/mybatis-3/zh/index.html   

首先到    https://github.com/mybatis/mybatis-3/releases   去下载jar包   除了mybatis的jar包之外还需要连接MySQL的jar包

   所有的框架要连接数据库都需要驱动包,除非框架内部已经集成

mybatis解压之后就是这样一个目录,我们只需要拷贝mybatis.jar    和   mysql-connector-java-6.0.6.jar 就可以了

lib目录中的包可以暂时不需要

环境配置

Mybatis中有两种配置文件:

全局配置文件 :  放在classPath的根路径下,  取名myBatis-config.xml / myBatis.xml等               
全局配置文件内容包括:1、全局配置信息 2、属性配置信息3、插件配置信息  4、环境配置信息(事务+连接池)5、关联映射文件

myBatis映射文件 : 放在Mappper接口的位置,取名一般使用 xxxMapper.xml、UserMapper.xml以示是哪个对象的配置信息

myBatis映射文件内容 : 1、编写增删改查的sql语句 2、对象属性和数据库字段映射关系配置 3、缓存配置

新建resources目录,在该目录下创建全局配置文件   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>
 	<environments default="">
 		<environment id="my">
 			<!-- 配置连接池事务等 -->
 		</environment>
 		<environment id="test">
 			<!-- 配置连接池事务等 -->
 		</environment>
 		<environment id="produ">
 			<!-- 配置连接池事务等 -->
 		</environment>
 	</environments>
</configuration>

mybatis可以配置多个环境,通过修改 environments 的default属性来改变环境 

比如我现在配置三个环境,一个是我自己开发用、一个是测试环境、一个是生产环境,我只需要把default的属性设置为my

那么现在的环境就会使用我自己配置的my 的 连接池和事务等配置

transactionManager的Type设置是告诉MyBatis使用事务

dataSource的Type设置是告诉MyBatis用它自带的连接池

基本查询

编写UserMapper.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">
<!-- 
	namespace:表示该Mapper的唯一标识
 -->
<mapper namespace="com.xiaoww.myBatis.UserMapper">
	<!-- 
		select 标签标识这是一条查询语句
		id:标识该sql的唯一标识
		parameterType:传入的参数类型。这个属性是可选的,因为 MyBatis 可以通过 TypeHandler 推断出具体传入语句的参数,默认值为 unset。
		resultType:返回的数据类型(把结果集封装成什么对象)
		#{id}:传入的参数名
	 -->
	<select id="getById" parameterType="Long" resultType="com.xiaoww.myBatis.User">
		SELECT id,name,salary FROM user WHERE id = #{id}
	</select>
</mapper>

测试代码

public class App {
	public static void main(String[] args) throws IOException {
		//从classPath读取配置文件
		InputStream is = App.class.getClassLoader().getResourceAsStream("mybatis- 
                                                                            config.xml");
		//用来创建SqlSessionFactory
		SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
		//用来创建SqlSession  需要传入一个流对象,也就是mybatis-config.xml
		SqlSessionFactory sessionFactory = factoryBuilder.build(is);
		//类似于connection对象
		SqlSession session = sessionFactory.openSession();
		//查询一个对象使用  UserMapper.xml 的 namespace再加上 select语句的id
		User selectOne = session.selectOne("com.xiaoww.myBatis.UserMapper.getById", 1L);
		System.out.println(selectOne);
		//资源关闭
		session.close();
	}
}

这样就完成了最基本的查询操作,可能会出现的问题:mysql驱动包版本错误

而如果我想查询所有   只需要在UserMapper中添加

<select id="listAll" resultType="com.xiaoww.myBatis.User">
        SELECT * FROM user 
</select>

测试代码改为

List<User> list= session.selectList("com.xiaoww.myBatis.UserMapper.listAll");
for (User user : list) {
     System.out.println(user);
}        

日志配置

     myBatis的日志操作也非常方便,只需要在全局配置中声明使用哪个日志实现就可以了

在mybatis-config.xml中   configuration  元素下面添加一个

<settings>
        <setting name="logImpl" value="LOG4J"/>
</settings>

使用log4j,然后拷贝log4j-1.2.17.jar到项目中去,在resources目录下添加log4j.properties 配置日志输出格式等

可以参考我的另外一篇文章https://blog.csdn.net/qq_39205291/article/details/82146656

然后将日志级别改为  DEBUG或者更低,再次运行就会看到DEBUG级别日志(包括执行的sql、查询到的结果集等)

#{id}所表达的意思是什么

     在Mapper.xml中使用#{id}来传入参数,那么#{id}到底代表的是什么?

           这种写法叫OGNL,OGNL是Object-Graph Navigation Language的缩写,它是一种功能强大的表达式语言,通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。

           假如有一个员工对象 

               employee{

                       id:1,

                       name:阿黄,

                       dept:{

                          id:10,

                          name:应用开发部

                       }

               }

       当前上下文中获取该员工名称就可以使用#{name}  ,而获取员工部门则可以使用#{dept.name}。

       如果当前上下文对象是javaBean,则#{属性名}取值

       如果当前上下文对象是map,则#{key}取值

       如果当前上下文对象是简单类型,则直接取值  和  #{xxxx}里面的xxxx无关   所以where id = #{xx} 也可以查询出来

获取SqlSession工具类

   SqlSessionFactoryBuilder对象只是为了创建SqlSessionFactory,在创建好之后便不会再使用;

   SqlSessionFactory在整个应用中应该只存在一个(类似于DataSource),所以我们要将它定义为static

   SqlSession对象是线程不安全的,所以无法使用单例模式,每个请求或方法都需要新创建一个SqlSession

public class MyBatisUtil {
	private static SqlSessionFactory sessionFactory = null;
	static{
		InputStream in = MyBatisUtil.class.getClassLoader().getResourceAsStream("mybatis- 
                                                                              config.xml");
		sessionFactory = new SqlSessionFactoryBuilder().build(in);
	}
	public static SqlSession getSqlSession(){
		return sessionFactory.openSession();
	}
}

修改             

    @Test
	public void update(){
		User u = new User();
		u.setId(1L);
		u.setName("安其拉");
		u.setSalary(new BigDecimal("1111"));
		SqlSession session = MyBatisUtil.getSqlSession();
		session.update("com.xiaoww.myBatis.UserMapper.update", u);
		session.commit();
		session.close();
	}
    UserMapper.xml
    <update id="update" parameterType="com.xiaoww.myBatis.User">
		UPDATE user SET name = #{name},salary=#{salary} WHERE id = #{id} 
	</update>

 删除

    @Test
	public void delete(){
		SqlSession session = MyBatisUtil.getSqlSession();
		session.update("com.xiaoww.myBatis.UserMapper.delete", 1L);
		session.commit();
		session.close();
	}
    <delete id="delete" >
		DELETE FROM user WHERE id = #{id} 
	</delete>

 

修改、保存 和删除是需要提交事务的,由于 sessionFactory.openSession()  默认是不自动提交事务,需要手动提交  session.commit();

保存

    @Test
	public void save(){
		User u = new User();
		u.setName("安其拉");
		u.setSalary(new BigDecimal("1111"));
		System.out.println(u);//此时的id为null
		SqlSession session = MyBatisUtil.getSqlSession();
		session.update("com.xiaoww.myBatis.UserMapper.save", u);
		session.commit();
		session.close();
		System.out.println(u);//此时的id为数据库返回的id
	}
    <!--
        useGeneratedKeys表示是否需要返回数据库生成的主键
        keyProperty 表示该对象的哪个属性是主键
    -->
	<insert id="save" parameterType="com.xiaoww.myBatis.User" useGeneratedKeys="true" 
                                                                       keyProperty="id">
		INSERT INTO user (name,salary) values (#{name},#{salary})
	</insert>

自定义别名           

     在UserMapper.xml中的resultType都是全限定名称 com.xiaoww.myBatis.User ,这样每次写起来都比较麻烦

     可以在全局配置mybatis-config.xml中来配置一个别名   type为全限定名,alias为别名

   如果使用的是package元素,则是为本包中所有类都取别名,别名名称为该类名首字母小写

<typeAliases>
    <typeAlias type="com.xiaoww.myBatis.User" alias="User"/>
</typeAliases>
或者
<typeAliases>
		<package name="com.xiaoww.myBatis"/>
</typeAliases>

这样就可以在UserMapper.xml中使用User作为resultType或paramterType了

注意:configuration元素的子元素是有先后顺序的,如果把typeAliases元素写在settings元素前面就会报错

别名不区分大小写

一些java中的数据类型在MyBatis中的别名

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

例如:   查询一个表的总数据量     此时必须设置 resultType  根据上面的别名可以写作integer或者int或者_integer或者_int

    @Test
	public void count(){
		SqlSession session = MyBatisUtil.getSqlSession();
		int a = session.selectOne("com.xiaoww.myBatis.UserMapper.count");
		System.out.println(a);
		session.close();
	}
    <select id="count" resultType="integer">
		SELECT count(id) FROM user 
	</select>

资源引用

     如何从properties文件中读取这些参数配置   

在全局配置mybatis-config中添加     <properties resource="db.properties"/>  引入properties文件

将配置提取到properties文件中即可

resultMap映射

   当对象的属性与数据库的字段名不一致应该怎么办?

   根据我们以前学过的知识,可以在查询sql中  使用字段别名让其与属性名保持一致

MyBatis提供了对象的映射关系,可以在xml中便捷处理这种映射

注意:resultMap中还有个id的标签用来描述对象的id映射关系可以提高映射效率,最终的映射关系应该是

使用Mapper接口

    在前面测试方法时我们的代码是这样的

可以看到String类型很容易写错   在编译时期也无法被检测出来

而参数为类型为Object类型, 在编译时期也同样无法被检测出来

这时候就需要有Mapper接口来限制    首先分包

将UserMapper.xml的namespace改为UserMapper接口的全限定名

在接口中定义方法

在Mapper.xml中定义同名xml查询方法

将测试方法改为

这样就跟以前调用Dao接口是一样的方式了,而这两个问题也都解决了

再定义个getById方法

多参数处理

很多的Dao方法都会涉及到多个参数的处理, session的CRUD方法也都是只允许传入一个参数,那么遇到多个参数应该怎么处理

首先: 可以将多个参数封装为一个对象,以登录为例

其次: 可以将多个参数封装为一个Map,注意Key值要和#{value}中的value一致

还可以使用MyBatis的方式,该方式的原理就是将参数封装到Map中,与方式二一样,只是写法不同而已

#和$的区别

  在Mapper.xml中写sql   取值的时候  使用#{}    其实也可以使用${}      

select * from user where username = ${username} and password = ${password}

那么这两个有什么区别呢

     #和$符号都可以把值取出来,但是#{}会把传入的数据作为参数   并且会为参数加上单引号

     select * from user where username = ? and password = ?

    而${}会把取出的值作为sql的一部分   并且不会为参数加上单引号

    select * from user where username = 123 and password = 456

如果你传入的参数中包含sql关键字,比如order by 、desc、group 等  则必须使用${} 其他情况一律使用#{}

简单来说    只有当使用#{}无效时,才会考虑使用${}

注解开发myBatis (不推荐)

    注解开发,就等于把之前xml里面写的sql、设置的属性搬到java代码里面来了

    既然没了Xml,唯一可以写代码的地方就是Mapper接口里面了 

首先,在全局配置文件中将UserMapper.xml注释掉,然后添加一个class对应我们的Mapper接口

然后直接将代码写到方法上,贴上注解insert  ,注意:要将主键返回需要设置两个属性useGeneratedKeys、keyProperty

贴上注解delete

贴上注解update

贴上注解select、select我们需要配置对象映射关系   使用@Results注解进行配置

那么如果我需要查询所有  是不是也得重新写一遍映射配置呢

   我们知道在xml中的resultMap可以设置一个id,@Results也可以设置一个id

所以最正确的写法应该是在@Results上设置一个id

下面用到的地方可以直接使用resultMap引用

条件语句     

  先将表结构改为

   if 条件语句   与java中的if使用方式一样

   跟据user表中的type查询结果集,如果为null,则当前参数无效

   

但是,如果现在  type==0   而    lastLoginTime  不为null  则sql 变成了

select id,username,pwd,type,lastLoginTime from user and lastLoginTime >= ?      没有了where关键字

这个问题在开发中也是经常遇到

myBatis提供了解决方案

使用<where>标签 将条件语句包含在内部

这样在生成sql  的时候,如果检测到没有where 关键字  则在条件之前添加where 关键字

                                       如果检测到条件语句以and 或者 or 开头  则会用where 替换第一个  and 或者 or

所以我们的sql只需要写  and  就可以了

 choose条件语句

  相当于  if else 语句

  写法

set标签       

 我们之前的update语句一般是这么写的

如果我在修改lastLoginTime的时候password为null,或者我想修改 password 的时候lastLoginTime 为null 。那么实际上是不应该把null该写到数据库中的,所以我们会加个判断

会变成这个样子

但是这样如果 password 为null  ,sql则会变成 

 update user set password = #{password}, where id = #{id}     注意#{password}, 会多出来一个逗号

所以set标签就出现了,使用set标签之后,它可以在所有条件前面添加一个set,并且会把最后一个逗号给去掉

测试传入lastLoginTime值为null   从日志可以看出来是对的

set 和 where这两个标签是怎么实现的呢

  其实他们就是trim标签的实现

  我可以把上面的update语句改写成这样

那么trim 到底是怎么一回事

 <trim prefix="set" prefixOverrides="" suffix="" suffixOverrides=",">

trim标签的四个属性 

 prefix:类似于SpringMvc的WEB-INF/pages前缀 、在sql前面加上个  where、set 

 prefixOverrides:如果sql前面是以AND或者OR打头,则把他替换掉   ,  而替换他的值就是 prefix 属性值

例子:  <trim prefix="ABC" >
           123456
         </trim>会被拼接为ABC123456

         <trim prefix="ABC"   prefixOverrides="123">
           123456
         </trim>会被拼接为ABC456

同理 suffix="" suffixOverrides="," 为后缀、后缀替换属性

迭代 foreach标签

   当需要一次性删除多个元素的时候   delete from user where id in(?,?,?);   这时 (?,?,?) 形式的参数 就需要用foreach来拼接

还有mysql批量插入的时候 insert into user(id,name) values (?,?),(?,?),(?,?);  这样可以一次性插入三条数据

<sql>和<include> 标签的使用

                   

一对多的对象关系处理         

     先来张部门的表  department   字段  id,name

     再来张员工的表  employee      字段  id,name,dept_id       一个部门可以包含多个员工,所以首先先保存一个部门和两个员工

public interface DepartmentMapper {
	public void save(Department dept);
}

<mapper namespace="com.xiaoww.myBatis.mapper.DepartmentMapper">
    <insert id="save" useGeneratedKeys="true" keyProperty="id">
        insert into department (name) values (#{name})
    </insert>
</mapper>     

public interface EmployeeMapper {
	public void save(Employee e);
}

<mapper namespace="com.xiaoww.myBatis.mapper.EmployeeMapper">
    <insert id="save">
        insert into employee (name,dept_id) values(#{name},#{dept.id})
    </insert>
</mapper>     

测试的时候我们需要先保存部门再保存员工,因为员工需要先设置部门才能保证数据的正确性

@Test
	public void save(){
		SqlSession sqlSession = MyBatisUtil.getSqlSession();
		DepartmentMapper mapper = sqlSession.getMapper(DepartmentMapper.class);
		Department dept = new Department();
		dept.setName("java部门");
		mapper.save(dept);
		EmployeeMapper mapper2 = sqlSession.getMapper(EmployeeMapper.class);
		Employee e = new Employee();
		e.setName("张三");
		e.setDept(dept);
		Employee e2 = new Employee();
		e2.setName("李四");
		e2.setDept(dept);
		mapper2.save(e);
		mapper2.save(e2);
		sqlSession.commit();
	}

保存完之后我们来查询一个部门员工

              员工里面包含了部门信息

那么如果使用   select id,name,dept_id from employee where id = #{id}   查出来之后部门信息一定是null

此时我可以使用resultMap将dept_id 赋值 给   dept.id

                   

查询出结果为

     

可以看到已经有了部门,但是部门名字还是没有查出来

最简单的方式无非就是直接用id把部门查出来再设置给Employee对象

myBatis的处理方式   使用association关联复杂查询

properties   就是Employee对象中的属性名称

javaType    关联的对象类型

select         使用哪条查询语句   、使用下图这条查询语句

column       传递的参数

连起来就是   使用select * from department where id = #{id}   传入参数为 Employee的dept_id 查询返回一个Department对象  封装到Employee 的dept属性中去,这样就解决了对象的关联问题

思考: 这样每次查询等于要发两条sql?

      如果使用select * from employee  会是什么样的情况    

测试过后发现,如果部门都是不一样的,就会每次都去查询 ,如果有一样的,则不用再次发送sql。

也就是说:如果员工很多,且每个部门都不一样,就会发送  员工数(每个员工查一次部门)+1(查询所有员工)条sql去查询

这样是很影响效率的     

解决 :   使用关联查询     我们查询多张表一般是使用sql关联 来查询   

使用association来关联映射   

一对多的查询

     还是使用刚才的员工和部门的关系,使用部门作为主表的时候,部门里面可以存在多个员工,就变成了一对多关系

    

在部门对象中加入员工集合      初始化list的原因是  使用elps添加的时候不会报空指针,也可以不初始化

  员工类

跟上面一对一查询差不多,设置一下查询sql、传入参数、属性名

另外一种方式,使用关联查询

注意:一对多使用的是collection (类型属性ofType) 、 一对一是 association(类型属性javaType)

延迟加载

   上面说到一对一和一对多的关联时,第一种查询方式其实就是查询了两次

   第一次查询员工,第二次使用员工的部门id去查询部门

   第一次查询部门,第二次使用部门的id去查询部门下面的所有员工

也就是说我只要查询员工,就会把所在部门查出来,只要查询部门就会把所有员工一起查出来(非常累赘)

  我使用部门的时候不一定要看该部门下的员工,这样会每次浪费数据库资源去查询大量的无用sql

  mybatis提供了延迟加载的方式来处理这一问题 :

                    当需要使用该关联属性的时候才加载该关联属性

  跟他所匹配的还有一个属性叫

 lazyLoadingEnabled开启时,会启用延迟加载。但是aggressiveLazyLoading也需要设置为false;

 aggressiveLazyLoading的意思是,虽然是延迟加载了,但是你无论调用对象的哪个属性,我都会马上加载关联属性。

 此时测试,调用对象的getName只发送了一条查询sql

但是打印整个对象还是发送了两条sql(此时toString方法并没有getEmployees)

这是因为还有一个配置lazyLoadTriggerMethods   他配置了使用哪个方法时触发延迟加载方法

  我们将它配置为  clone 就可以了 , 只有在调用clone方法时才会触发

缓存

mybatis的缓存分为一级和二级缓存    

        一级缓存 : 也称之为本地缓存,作用域为sqlSession 默认开启且不能关闭 可以使用sqlSession.clearCache();清除缓存

        随便找一个测试方法,使用相同的参数再查一次之前查过的对象,会发现没有再次发送sql

我这里重写了toString方法,如果没有重写的话你会发现 对象的hashCode都是同一个,这说明缓存直接保存的就是Object

而不是通过Json等格式转换的对象,其实就是sqlSession中有个hashMap,保存一个Key 和 对象   当map中存在则直接取出

由于每次查询我们都会开启一个新的sqlSession,所以一级缓存对性能的提升非常有限

        二级缓存:mapper级别的缓存,同一个namespace共用一个缓存对象,不同的sqlSession也共享数据

        如何使用:首先在全局设置中开启二级缓存(默认开启)

1、<setting name="cacheEnabled" value="true"/>

2、在需要缓存的mapper文件中   加上 <cache />

3、需要缓存的实体类实现Serializable接口   (缓存需要配置大小,如果超出这个大小则会往磁盘中写,所以需要实现序列化)

查询两次如果分别看到命中率为0.0 和0.5 说明缓存已经开启成功

在mapper的<cache/>元素中也可以配置相关属性

eviction:缓存回收策略   可用的值如下图    默认LRU(推荐)     

flushInterval:刷新策略  以毫秒为单位     多少毫秒不使用将清空该缓存
size:缓存大小,默认1024(推荐)
readOnly:是否只读     只读对象不能被修改(效率高)   默认false

列表查询必须参数一致才会使用缓存,所以一般不推荐使用。浪费内存   如果有多条件列表查询不需要使用缓存就直接在select标签上加上useCache="false"

默认情况下,执行insert、update、delete都会刷新缓存,如果想阻止缓存刷新,可以在标签上加flushCache="false"

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值