框架
首先Mybatis是一套框架。框架是为了解决软件开发中遇到的一些问题而生的,框架一般会封装很多细节,能让开发者可以减少工作量、提高工作效率。阿里巴巴内部对这个框架用了很多。
- 表现层:代表框架:
SpringMVC,用以展示数据。 - 业务逻辑层:主要靠
service和javabean来完成,用来完成业务的处理。 - 持久层:
mybatis,和数据库进行交互,向外提供一个DAO的接口。
目前在Java中,原生的技术有JDBC,包含了Connection、PreparedStatement之类的,然后当然有一些简单封装的工具类,比如Apache的DBUtils等。
而mybatis在内部封装了jdbc,使得开发者只需要关注于sql语句本身,其它的加载驱动、创建连接等,我们都可以通过注解或者配置xml文件来进行处理。还有一个就是能够直接返回一个对象,便于开发人员直接操作java对象。
之前的流程
我们连接数据库,大体可以分成这几个步骤:
- 配置数据库的信息
- 连接到数据库
- 对数据库进行操作
- 获取了对应的数据之后进行处理
- 一些善后工作等
当然之前我们都是通过编写对应的代码,一些数据库的信息都是直接写死在了对应的代码里面,这样耦合很高,而且也不安全,需要切换的时候也非常不方便。
环境搭建
首先因为是和数据库相关的框架,相关的数据库必须得有,所以创建一个:
1 | -- 创建数据库 |
数据准备好了之后,就可以开始编写代码了,具体步骤如下:
创建项目并初始化
创建空maven工程并且写好数据库驱动依赖和mybatis的依赖。
首先的pom里面增加对应的依赖(还有mysql的驱动、Junit这些的自己添加):
1 | <dependency> |
并且在类路径下(一般推荐在resource下,如果不是在resource下面,那么默认情况下是不会打包进项目中的,这点切记,当然可以通过配置maven来让对应的后缀的文件加入到类路径中)创建一个SqlMapConfig.xml(当然叫什么其实是随意的,反正代码能找到就行),然后在里面配置好对应的数据库相关信息:
1 |
|
到这里准备工作就做好了。PS:上面那个<mappers>里面的映射关系是用来管理对应的sql语句的。
创建dao的接口和实体类
就是到项目下面,创建一个接口,这个接口后面就只需要定义对应的方法,比如查找的方法就可以了。不需要任何的实现方法,这里也是mybatis最与众不同的地方,它使用了配置文件来代替实现类完成对应的增删改查功能。
为对应的数据库封装对应的对象,因为我们最后肯定是要把数据库的内容读取出来到对象的嘛,那就先创建好这些对象,然后这些对象也可以在上面创建的接口中被使用。
创建对应的映射文件
这个文件就是上面初始化的配置里面mapper所对应的文件。在这个文件里面写好对应的sql语句,然后就可以执行里面的sql语句了。所以可以把这个文件当成是一座桥梁,把之前定义的接口和sql语句联系在了一起,而这个sql语句其实就是接口的实现函数。
一个demo如下:
1 |
|
可以看到上面我指定了namespace就是对应的接口,而里面的就是一个select语句。你看它为什么叫mapper,因为它就是连接了接口和对应的实现类。
到这里就完成了,值得注意的是:
- 在
Mybatis中,持久层的操作接口名称和映射文件也叫Mapper,所以UserMapper和IUserDao是一样的 - 映射配置文件的
mapper标签namespace属性的取值必须是mapper接口(即DAO接口)的全限定类名 - 映射配置文件的操作配置,id 属性的取值必须是
mapper接口的方法名
看上去是不是一头雾水?什么破玩意儿啊,我接下来用我自己的语言组织下。
首先,你要连接数据库,那么必须要指定驱动、url、用户名和密码,这是肯定省不了的吧?这就对应了上面说的主配置文件。然后对于一个类的方法,你首先必须找到一条sql语句与之对应吧?这个就是第二个配置文件的作用,它让一条sql语句和一个接口(当然也可以是类)中的方法产生了联系。而我们之后会有许许多多的类,也就是会有很多的xml配置文件,这些配置文件需要将自己所在的位置写入到主配置文件中,这样就完成了。
还觉得麻烦吗?对,反正我觉得很麻烦,那怎么办呢?用注解来简化下操作呗。
分析和还原
首先这是主要的一些代码:
1 | public static void main(String[] args) throws Exception { |
我们简单分析下,就可以自己来完成这个框架的简单替换了(即自己完成mybatis的核心功能)。一般来说,实际中我们推荐前三步使用工具类进行封装,这样需要的时候可以直接返回sqlSession进行使用。
整体流程是这样的:配置文件 -> SqlSessionFactoryBuilder -> SqlSessionFactory -> SqlSession ->操作数据库
- 读取xml配置我们可以用dom4j来完成。为了简便,可以直接硬编码….
- 创建工厂这步,我们只需要用到工厂模式就可以很轻松的完成啦
- 动态代理技术
首先,我们对主配置文件中的四个信息(driver、url、username和password)进行封装,构成一个configuration的类,然后构建一个工具类,能够获取connection对象:
1 | public class DataSourceUtil { |
本来是想用连接池的,但是这样会增加复杂度,后期可以修改下。
其次,我们封装一下Map<String, Mapper> mappers这个数据结构:它的键是全限定类名+方法名,然后Mapper里面是一个sql语句和一个返回类型(也就是数据库表中的数据所对应的类名)。这样我们就能知道每个方法所对应的sql语句以及应该给查询出来的语句赋值到哪个对象上去了。
然后,也是最最重要的一部分,就是使用动态代理的方法来加强函数的功能。这部分就是在getMapper那个方法中,为其加入数据库查询的部分,然后加入返回值的对象即可。
CURD
插入操作
定义一个函数来保存一个对象到数据库里,只需要在接口中创建一个方法,比如是void saveUser(User user);,然后在配置文件里面加入一个insert标签:
1 | <insert id="saveUser" parameterType="cn.chenlangping.domain.User"> |
其中id是方法名字,而第二个参数是真正参数的全限定类名。最后在#{}内的内容,就是属性的名字。最后使用的时候不要忘记,默认是关闭了自动提交事务的,所以记得手动提交下事务。
更新操作
一样的方法,也是在接口中创建一个方法,然后是一样的,只是标签有点不同。
1 | <update id="updateUser" parameterType="cn.chenlangping.domain.User"> |
删除操作
删除操作有点特殊,因为我们只需要知道id即可删除对象,而且仅仅需要一个参数,所以仅仅是一个占位符而已。
1 | <delete id="deleteUser" parameterType="java.lang.Integer"> |
map的妙用
上面的一些我们在查询的时候,需要自己构造一个对应的对象,如果我们仅仅需要这个对象的某一些属性来作为参数查询,而这个对象的属性非常多,那么就可以使用map来进行简化操作了。
配置文件详解
核心配置文件
环境配置(environments)
MyBatis 可以配置成适应多种环境,现实中最常用的就是测试环境和生产环境的一键切换。尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
属性(properties)
属性可以在很多地方定义,然后就可以被框架所使用。当然会存在多个属性之间相互覆盖的问题,它们之间的顺序是这样的:
- 首先读取在 properties 元素体内指定的属性。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
- 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
设置(settings)
设置我们一般只会使用其中的缓存启用、延迟加载等少量设置,需要的时候再进行调试就可以了。
类型别名(typeAliases)
如果不想写很长的全限定类名,那么就可以使用它来简化操作。可以一个类一个类来自己定义;当然也可以对一个包下的所有都去掉它们的全限定类名。这里需要注意的是,本来不同的包下可以存在相同类名的类;但是由于去掉了包名,所以导致类名冲突,这样子mybatis的处理方式是抛出异常。
映射器(mappers)
我们可以指定对应的xml文件的位置来找到映射器,也可以通过使用映射器接口实现类的完全限定类名、还可以通过指定包名。
这里需要注意的是,如果通过类名来找到对应的映射器,那么xml文件和类名需要完全一致才可以。
属性名和字段名不一致
对象的对象
比如我现在用另外一个对象封装了user,然后想利用user的name来进行查询,那么可以使用一个简洁的语法,即OGNL(Object Graphic Navigation Language)来进行处理,简单来说就是使用对象名.属性即可访问成功。比如我要访问user的username属性,我不再需要user.getUsername()方法了,而是只需要输入user.username即可。
其实上面已经在用了,只不过由于只有一层,所以不明显。
如果实体类属性和数据库列名不一致呢?
前面我们都是让类的属性和数据库列名相同,这样是为了方便建立对象,那么如果就是不一样呢?有两种解决办法,第一种就是用数据库的别名,第二种当然是进行配置啦。
1 | <resultMap id="whatever" type="cn.chenlangping.domain.User"> |
在select等语句上面写上这块,然后在update啊这种DML语句中,把resultType删了换成resultMap并且写上id,这样mybatis就知道了。
根据对象条件
假设我传入了一个user对象,而这个对象中我只设置了名字,也就是我希望能根据user中存在的信息来找用户,那么应该怎么做?显然上面的这些都不行了,需要引入特殊的标签<if>。
1 | <select id="findUserByCondition" parameterType="cn.chenlangping.domain.User" resultType="cn.chenlangping.domain.User"> |
上面的例子就能很好的说明问题,如果传入的user对象的username不是空(按照java的语法),那么把下面if标签里面的内容拼接到原始的那句话上面。
仔细看上面的内容,你会发现有个1=1 看上去是不是贼不舒服?mybatis为我们提供了<where>标签来解决,所以修改一下就会这样:
1 | <select id="findUserByCondition" parameterType="cn.chenlangping.domain.User" |
实际中可能还会碰到这种需求,比如select * from user where id in (1,2,3);,这种sql语句在mybatis中应该怎么写呢?用<foreach>标签即可。
连接池
连接池首先它必须是线程安全的,不然会导致两个线程能够拿到同一个。而且它本身也是一个队列,这样就能够循环使用各个连接啦。而mybatis当然也提供了连接池的配置,在主配置文件里的datasource标签中就可以进行配置,type就是用来表示是采用什么方式来进行连接的,它可以有以下三种取值:
- POOLED:采用传统Java datasource中的连接池。mybatis有针对这个规范的实现。
- UNPOOLED:不用连接池,虽然它实现了javax.sql.datasource接口,但是实际上它每次都是单个连接,并没有使用所谓的连接池。
- JNDI:采用服务器提供的JNDI技术实现,来获取datasource对象,不同服务器不同;如果不是web工程,将无法使用。比如tomcat使用的是叫dbcp连接池。
多表关联
假设现在有一张用户表,一张账户表;两者是多对一的关系,即一个用户可以有多个账户。显然根据数据库的知识我们可以知道,账户表中需要有一个外键来指向用户表。
1 | -- 创建账户表,外键为uid,关联用户表的id |
然后是在Java中,必然我们需要建立两个类来分别对应用户和账户。同时针对这两个类我们也需要有两个配置文件。
最后再新建一个类,能够存入查询出来的数据。
缓存
适用于缓存的数据:不经常改变、而且改变的话对最终结果影响不大的数据才使用用缓存。
一级缓存
sqlSession本身就提供了一块缓存区域,该区域本质上是一块Map。可以通过sqlSession.clearCache()方法来清除缓存。
二级缓存
sqlSessionFactory也提供了缓存,叫做二级缓存,但是默认是不支持的。需要在主配置文件中和对应的类配置文件中配置,然后还需要让语句支持。二级缓存中存放的是数据,而不是对象。