MyBatis笔记
1、orm工具的基本思想
从配置文件(通常是XML配置文件中)得到 sessionfactory,由sessionfactory 产生 session,在session 中完成对数据的增删改查和事务提交等,在用完之后关闭session 。
//1.读取配置文件 String resource = "SqlMapConfig.xml"; InputStream is = Resources.getResourceAsStream(resource); //2.根据配置文件创建SqlSessionFactory SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = builder.build(is); //3.创建SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); /* * 4.SqlSession操作数据,执行增删改查 * 第一个参数: statement ID , namespace.statementID * 第二个参数: param */ User user = sqlSession.selectOne("com.oscar.mybatis.dao.UserMaper.findUserById",1); System.out.println(user); //5.关闭SqlSession sqlSession.close();
2、工作流程
(1)加载配置并初始化
触发条件:加载配置文件
置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的sql语句、结果映射配置),存储在内存中。
(2)接收调用请求
触发条件:调用Mybatis提供的API
传入参数:为SQL的ID和传入参数对象
处理过程:将请求传递给下层的请求处理层进行处理。
(3)处理操作请求 触发条件:API接口层传递请求过来
传入参数:为SQL的ID和传入参数对象
处理过程:
(A)根据SQL的ID查找对应的MappedStatement对象。
(B)根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。
(C)获取数据库连接,根据得到的最终sql语句和执行传入参数到数据库执行,并得到执行结果。
(D)根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果。
(E)释放连接资源。
(4)返回处理结果将最终的处理结果返回。
3、MyBatis配置文件说明
MyBatis的全局配置文件,主要是配置了数据源、事务和映射文件,其实在SqlMapConfig.xml中还可以配置很多信息,如:
properties(属性)数据库连接参数单独配置在db.properties中,便对参数进行统一管理,其它xml可以引用。
<properties resource="db.properties"/>,使用时 用 value="${db.driver}" 。
在classpath下定义db.properties文件如下:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/shop
jdbc.username=sa
jdbc.password=dacude2017
~~~~~~~~~~~~我是完毕分割线~~~~~~~~~~~~~~
properties特性:
MyBatis 将按照下面的顺序来加载属性:
1、在 properties 元素体内定义的属性首先被读取。
2、然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。
3、最后读取parameterType传递的属性,它会覆盖已读取的同名属性。
因此,通过parameterType传递的属性具有最高优先级,resource或 url 加载的属性次之,最低优先级的是 properties 元素体内定义的属性。
一般不要在properties元素体内添加任何属性值,只将属性值定义在properties文件中。在properties文件中定义属性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX。
settings(全局配置参数)--开启日志和二级缓存的配置语句,开启延迟加载等。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"></setting>
<setting name="cacheEnabled" value="true"></setting>
</settings>
typeAliases(类型别名)
typeHandlers(类型处理器)--输入输出的映射对应。
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
Mapper配置的几种方法:
1>、使用相对于类路径的资源,如:<mapper resource="sqlmap/User.xml" />
2>、使用完全限定路径,如:<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />
3>、单个别名定义,使用mapper接口类路径,如:<mapper class="com.luchao.mybatis.first.mapper.UserMapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
通过mapper接口加载单个 映射文件遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在一个目录 中。上边规范的前提是:使用的是mapper代理方法。
4>、注册指定包下的所有mapper接口,如:<package name="cn.itcast.mybatis.mapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
批量加载mapper,指定mapper接口的包名,mybatis自动扫描包下边所有mapper接口进行加载,遵循一些规范:需要将mapper接口类名和 mapper.xml映射文件名称保持一致,且在一个目录中。上边规范的前提是:使用的是mapper代理方法。
4、配置文件参考
<?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> <typeAliases> <!-- 别名 --> <typeAlias alias="UserInfo" type="com.oscar.mybatis.models.UserInfo" /> <typeAlias alias="Post" type="com.oscar.mybatis.models.Post" /> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <!--模式不知道就别动 --> <property name="driver" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" /> <property name="url" value="jdbc:sqlserver://localhost:1433; DatabaseName=OtherDb" /> <property name="username" value="sa" /> <property name="password" value="dacude2017" /> </dataSource> </environment> </environments> <mappers> <mapper resource="com/oscar/mybatis/dao/IUserInfoDao.xml" /> </mappers> </configuration>
5、映射文件参考
<?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"> <!-- 命名空间,分类管理SQL语句 --> <!-- 注意:在mapper代理时,namespace具有特殊及重要的作用 --> <mapper namespace="com.oscar.mybatis.dao.UserMaper"> <!-- ===============根据ID查询用户信息=============== select: 表明一个MapppedStatement。 id: statement的id,要求在命名空间内唯一。 parameterType: 入参的java类型。 resultType: 查询出的单条结果集对应的java类型。 #{ }: 表示一个占位符。 #{id}: 表明该占位符接受的参数名称为id。注意:如果参数为简单类型#{}里面的参数名称可以是任意定义的。 --> <select id="findUserById" parameterType="int" resultType="com.oscar.mybatis.models.UserInfo"> SELECT * FROM USER WHERE id = #{id} </select> <!-- 根据用户名称模糊查询 ${}:表示一个拼接符 ${value}: ${}中的value表示输入参数的参数名称,注意:如果参数为简单类型${}里面的参数名称只能是value ${}存在SQL注入的风险,但是特殊情况必须使用${},比如order by 后面必须是${columnName} --> <select id="findUserByName" parameterType="java.lang.String" resultType="com.kiwi.domain.User"> SELECT * FROM USER WHERE username LIKE '%${value}%' </select> </mapper>
6、基本使用方法
方法1原始方式: 直接用session去执行sql。
reader = Resources.getResourceAsReader("config/MyBatisConfigure.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); SqlSession session = sqlSessionFactory.openSession(); UserInfo user = (UserInfo) session.selectOne("com.oscar.mybatis.models.UserMapper.GetUserByID", 1);
方法2注解方式,用mapper去执行sql。
创建一个接口,引入注解,在注解里写语句,注意方法名,把接口映射 加入到session中。
使用注解时,没有dao的实现类。
reader = Resources.getResourceAsReader("config/MyBatisConfigure.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
sqlSessionFactory.getConfiguration().addMapper(IUser.class);//加入mapper
SqlSession session = sqlSessionFactory.openSession();
IUser iuser = session.getMapper(IUser.class); //获取到mapper
User user = iuser.getUserByID(1);
System.out.println("名字:"+user.getName());
session.close();
注1:使用注解时,xml的文件名与接口文件名称一致,xml的命名空间是接口文件的命名空间+自身名称。
如,IUserInfoDao.xml,IUserInfoDao.java, 和 namespace="com.oscar.mybatis.dao.IUserInfoDao"
注2:在增加,更改,删除的时候需要调用 session.commit() 来提交事务,这样才会真正对数据库进行操作提交保存,否则操作没有提交到数据中。
Mapper代理的开发方式,程序员只需要编写mapper接口(相当于dao接口)即可。Mybatis会自动的为mapper接口生成动态代理实现类。不过要实现mapper代理的开发方式,需要遵循一些开发规范。
(1)mapper接口的全限定名要和mapper映射文件的namespace的值相同。
(2)mapper接口的方法名称要和mapper映射文件中的statement的id相同。
(3)mapper接口的方法参数只能有一个,且类型要和mapper映射文件中statement的parameterType的值保持一致。
(4)mapper接口的返回值类型要和mapper映射文件中statement的resultType值或resultMap中的type值保持一致。
7、关联查询
7.1应用场景:首先根据用户 ID 读取一个用户信息,然后再读取这个用户所发布贴子(post)。
UserInfo的model里要有 private List<Post> posts;,
xml配置里,返回类型用resultMap, 自定义一个map。使用 collection 。
<!-- User 级联文章查询 方法配置 (一个用户对多个文章) --> <resultMap type="UserInfo" id="resultUserMap"> <result property="id" column="id" /> <result property="name" column="name" /> <result property="dept" column="dept" /> <result property="phone" column="phone" /> <result property="website" column="website" /> <collection property="posts" ofType="Post" column="userid"> <id property="id" column="pid" javaType="int" jdbcType="INTEGER"/> <result property="title" column="title" javaType="string" jdbcType="VARCHAR"/> <result property="content" column="content" javaType="string" jdbcType="VARCHAR"/> </collection> </resultMap> <select id="getUserWithPost" resultMap="resultUserMap" parameterType="int"> SELECT u.*,p.* ,p.id as pid FROM userInfo u, post p WHERE u.id=p.userid AND u.id=#{userid} </select>
7.2应用场景:首先根据帖子 ID 读取一个帖子信息,然后再读取这个帖子所属的用户信息。
Post的model里要有private UserInfo userInfo;
xml配置里,返回类型用resultMap, 自定义一个map。,类似3.1,但关键字使用 association。
注1,列名重复时,必须用别名 区分,这样才能关联上--不然就只能查到一条数据。
注2:注意关联词,colection时用的ofType, association时用的javaType。
8、与Spring集成
8.1用到的类库
8.2需要一个Spring配置文件 SpringConfigure.xml, 的内容如下:
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"> <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" /> <property name="url" value="jdbc:sqlserver://localhost:1433; DatabaseName=OtherDb" /> <property name="username" value="sa" /> <property name="password" value="dacude2017" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--dataSource属性指定要用到的连接池--> <property name="dataSource" ref="dataSource" /> <!--configLocation属性指定mybatis的核心配置文件--> <property name="configLocation" value="config/applicationContext.xml" /> </bean> <bean id="userMaper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <!--sqlSessionFactory属性指定要用到的SqlSessionFactory实例--> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> <!--mapperInterface属性指定映射器接口,用于实现此接口并生成映射器对象--> <property name="mapperInterface" value="com.oscar.mybatis.dao.UserMaper" /> </bean>
注:配置文件从下往上读,
id为 userMaper的 bean,需要一个映射接口和一个sqlSessionFactory,接口指定配置文件路径,sqlSessionFactory往上找;
sqlSessionFactory 有dataSource和核心配置文件的路径。
dataSource 就是jdbc驱动的配置,服务类型,地址,用户密码。
8.3使用代码:
ApplicationContext ctx = new ClassPathXmlApplicationContext("config/SpringConfigure.xml");
UserMaper userMaper = (UserMaper) ctx.getBean("userMaper");
// 测试id=1的用户查询,可根据数据库中的情况修改.
UserInfo user = userMaper.getUserById(1);
System.out.println("获取用户 ID=1 的用户名:"+user.getName());
9、动态SQL语句
9.1. if 语句 (简单的条件判断) <if test="sex!= null and sex != '' "> AND u.sex = #{Sex, jdbcType=INTEGER} </if> <if test="birthday != null "> AND u.birthday = #{birthday, jdbcType=DATE} </if> 9.2. choose (when,otherwize) ,相当于java 语言中的 switch ,与 jstl 中的choose 很类似。 从上往下,执行第一个TRUE的。 <choose> <when test="title != null"> and title = #{title} </when> <when test="content != null"> and content = #{content} </when> <otherwise> and owner = "owner1" </otherwise> </choose> 9.3. trim (对包含的内容加上 prefix,或者 suffix 等,前缀,后缀) trim 是更灵活用来去处多余关键字的标签,它可以用来实现 where 和 set 的效果。 trim 元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是 prefix 和 suffix; 可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是 prefixOverrides 和 suffixOverrides;正因为 trim 有这样的功能,所以我们也可以非常简单的利用 trim 来代替 where 元素的功能。 <select id="dynamicTrimTest" parameterType="Blog" resultType="Blog"> select * from t_blog <trim prefix="where" prefixOverrides="and |or"> <if test="title != null"> title = #{title} </if> <if test="content != null"> and content = #{content} </if> <if test="owner != null"> or owner = #{owner} </if> </trim> </select> 9.4. where (主要是用来简化sql语句中where条件判断的,能智能的处理 and or ,不必担心多余导致语法错误) <select id="dynamicWhereTest" parameterType="Blog" resultType="Blog"> select * from t_blog <where> <if test="title != null"> title = #{title} </if> <if test="content != null"> and content = #{content} </if> <if test="owner != null"> and owner = #{owner} </if> </where> </select> 9.5. set (主要用于更新时) 主要功能和 where 标签元素其实是差不多的,把该逗号忽略。 update id="dynamicSetTest" parameterType="Blog"> update t_blog <set> <if test="title != null"> title = #{title}, </if> <if test="content != null"> content = #{content}, </if> <if test="owner != null"> owner = #{owner} </if> </set> where id = #{id} </update> 9.6. foreach (在实现 mybatis in 语句查询时特别有用) 动态 SQL 的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。 <select id="selectPostIn" resultType="domain.blog.Post"> SELECT * FROM POST P <WHERE> <if test="list!=null and list.size>0"> <foreach item="item" collection="list" open=" ID in (" separator="," close=")"> #{item} </foreach> </if> </where> </select> 允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。可以将一个 List 实例或者数组作为参数对象传给 MyBatis,当你这么做的时候,MyBatis 会自动将它包装在一个 Map 中并以名称为键。List 实例将会以“list”作为键,而数组实例的键将是“array”。
10、SQL片段
<!-- Sql片段: 可以将sql中任何部分放到sql片段中 建议: 不要将sql中的关键字放进去,比如 select where --> <sql id="whereClause"> <if test="user != null"> <if test="user.username != null and user.username != ''"> AND username LIKE '%${user.username}%' </if> <if test="user.sex != null and user.sex != ''"> AND sex = #{user.sex} </if> </if> </sql> <!--引用SQL片段 --> <select id="findUsersByNameAndSex" parameterType="userQueryVo" resultType="user"> SELECT * FROM user <!--wher标签:将后面第一个AND去掉,如果没有条件则把自己去掉 --> <where> <!-- refid: Sql片段的ID --> <include refid="whereClause"></include> </where> </select>
11、逆向工程
官方下载jar包mybatis-generator-core-1.3.5;
找一个配置文件改吧改吧;(配置文件略)
cmd方式,在命令行执行java -jar mybatis-generator-core-1.3.2.jar -configfile generator.xml –overwrite。
语句说明 , -configfile 后跟配置文件, -overwrite生成的文件覆盖原有的。
12、缓存
mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。key为hashCode+sqlId+Sql语句。
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
Mybatis默认支持一级缓存。sqlSession去执行插入、更新、删除,会清空SqlSession中的一级缓存。
12.1 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
12.2二级缓存是mapper级别的。
第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。
第二次调用相同namespace下的mapper映射文件中相同的SQL去查询用户信息。会去对应的二级缓存内取结果。
如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。
(1)在核心配置文件SqlMapConfig.xml开启二级缓存总开关
<settings>
<setting name="cacheEnabled" value="true"></setting>
</settings>
(2)在UserMapper映射文件中,开启二级缓存。POJO要实现序列化接口Serializable.
添加节点 <cache></cache>
(3)缓存命中率Cache Hit Radio
第一次缓存中没有记录,则命中率0.0
第二次缓存中有记录,则命中率0.5(访问两次,有一次命中)
(4)禁用二级缓存
statement中设置userCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。
(5)刷新二级缓存
statement中设置flushCache=true可以刷新当前的二级缓存,默认情况下如果是select语句,那么flushCache是false。如果是insert、update、delete语句,那么flushCache是true。
如果查询语句设置成true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。
如果查询语句设置成false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。
ps:如果是执行两次service调用查询相同 的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。
二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域。
两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同 的二级缓存区域中。
在sqlSession在执行close()方法才会将数据写入到缓存(二级缓存),读取二级缓存,涉及到序列化和反序列化。
可以通过设置缓存的readonly=true来设置缓存为只读。
应用场景:
对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。
二级缓存的局限性:
因为MyBatis的二级缓存是根据namespace来划分的,如果涉及到多个表的数据的管理,如果其他namespace一个表的数据进行了更新,这也就会出现脏读数据。如果其他表进行了更新把所有涉及这个表的管理的缓存都清空,这也缓存的利用率就比较低。
mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。
结语:我只想把我所知道的,尽量简洁清楚地表达出来。