MyBatis学习笔记
第一章 如何使用MyBatis? 1.导入MyBatis核心包及其依赖包 2.配置mybatis.xml文件 (1).配置驱动管理器 <transactionManager type="JDBC"/> (2).配置数据源 <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mytest"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> (3).配置映射文件 <mappers> <mapper resource="映射文件路径"> <!--例如: <mapper resource="com/xt/mapper/goodsMapper.xml"/>--> </mappers> 3.书写一个接口,包含将对数据库执行的操作方法 例如: /** * 添加商品信息 * @param goods * @return */ public Integer addGoods(Goods goods); 4.配置映射文件(这里是真正书写sql语句的文件) 包含的基本内容有对数据库进行增删改查的SQL语句。 例如: <mapper namespace="接口所在的包名+类名"> <!--insert语句--> <insert parameterType="sql语句中的参数类型"> //sql语句 insert into goods (goodsName,price,unit,madeTime,shui) values (#{goodsName},#{price},#{unit},#{date},#{shui}) //#{}表示占位符;${}表示sql串拼接 </insert> <!--delete语句--> <delete parameterType=""> ... </delete> <!--查询语句--> <select parameterType="参数类型" resultType="查询结果数据类型"> select 列名1,列名2... from 表名 where [条件] /** 此处注意: 如果数据库的列名与实体类的列名不一致,那么可以采用取别名的方式查询; 后续章节将会使用<resultMap> **/ </select> </mapper> 5.开始执行对数据库的操作 (1).读取配置文件 String resource = "mybatis.xml"; //配置文件的路径名称 InputStream inputStream = Resources.getResourceAsStream(resource); //读取配置文件 (2).获得session工厂,获得session SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); (3).执行 例如: IGoodsDaoMapper goodsDao = session.getMapper(IGoodsDaoMapper.class); goodsDao.insert(Goods goods); (4).session提交 session.commit();/**对数据进行增删改时,一定要记得提交**/ 第二章 查询详解(1) 1.在第一章中提到,在对数据库进行查询的时候,我们需要在查询语句中对查询的列名取别名,并且 别名的名称要与java实体类中对应。在本章,我们采用<resultMap>来对数据库进行基本的查询。 回顾第一章查询代码: //采用取别名的方式 <select parameterType="java.lang.Integer" resultType="com.xt.vo.goods"> select id as id,goods_name as goodsName,goods_price as goodsPrice from goods where id = #{id} </select> //使用<reusltMap></resultMap> 例如: <reusltMap id="goodsResult" resultType="com.xt.co.goods"> <id property="id" column="id"/> //主键 <result property="goodsName" column="goods_name"/> <result property="goodsPrice" column="goods_price"/> ...... </resultMap> //查询语句 <select parameterType="java.lang.Integer" resultMap="goodsResult"> select * from goods where id = #{id} </select> 采用这种方式进行查询,可以避免每次查询都需要进行取别名的繁琐;并且使用<resultMap>可以 单独提出,这样如果对一张表进行多次查询的时候,可以使用<resultMap>的id属性。这样,查询 是不是变得简单多了呢? 第三章 查询详解(2) 1.在上一章内容中,我们讲到,可以使用<resultMap>标签来查询,省去了查询时取别名的麻烦。之前 提到的查询都是对一张表进行简单查询。那么,对于两张表,甚至多张表进行查询时,我们往往会遇到 "多对一"或者"一对多"的查询。 例如:多个商品对应一种商品类型或者一种商品类型对应多个商品...... 2.多对一的查询 方式1: <association property="" column="" javaType="" select=""></association> property:实体类中对应的变量名称 column:数据库中对应的列名 javaType:该属性对应的实体类(包+类) select:对应的查询语句id 例如: <resultMap id="goodsResult" type="com.xt.vo.Goods"> <id property="goodsId" column="id" /> <result property="goodsName" column="goodsName" /> <result property="price" column="price" /> <result property="unit" column="unit" /> <result property="date" column="madeTime" /> <result property="shui" column="shui" /> <association property="goodsType" column="typeId" javaType="com.xt.vo.GoodsType" select="findGoodsTypeById"> </association> </resultMap> <select id="findGoods" resultMap="goodsResult"> select * from goods </select> <select id="findGoodsTypeById" parameterType="java.lang.Integer" resultMap="goodsTypeResult"> select * from tb_type where id = #{id} </select> 方式一的查询流程: 1.首先将goods表中的数据全部查询出来,包括typeId 2.将查询出来的每一个typeId作为参数传入到id="findGoodsTypeById"的<select>查询语句中查询, 并且每一个typeId传入时,都会进行一次查询。假如有3个,就会查3次;有10000个,就会查10000次...... 这种方式显而易见是不够优秀的。 方式2: <association property="" column="" javaType=""></association> 例如: <resultMap id="goodsResult" type="com.xt.vo.Goods"> <id property="goodsId" column="id" /> <result property="goodsName" column="goodsName" /> <result property="price" column="price" /> <result property="unit" column="unit" /> <result property="date" column="madeTime" /> <result property="shui" column="shui" /> <association property="goodsType" column="typeId" javaType="com.xt.vo.GoodsType"> <id property="id" column="id"/> <result property="typeName" column="typeName"/> </association> </resultMap> <select id="findGoods" resultMap="goodsResult"> select goods.*,tb_type.* from goods inner join tb_type on goods.typeId = tb_type.id </select> 方式2显然是在sql语句中进行了改进,避免了产生多次查询的情况。 3.一对多的查询 <collection property="" javaType="" ofType=""> ...... </collection> ofType:集合中元素类型 例如: <resultMap id="typeResult" type="com.xt.vo.GoodsType"> <id property="id" column="id"/> <result property="typeName" column="typeName"/> <collection property="goodsList" javaType="java.utils.ArrayList" ofType="com.xt.vo.Goods"> <id property="goodsId" column="id" /> <result property="goodsName" column="goodsName" /> </collection> </resultMap> <select id="findGoodsType" parameterType="java.lang.String" resultMap="typeResult"> select tb_type.*,goods.* from tb_type inner join goods on tb_type.id = goods.typeId </select> 第四章 查询详解(3)以及动态SQL 1.在对一张表进行查询的时候,我们通常会进行模糊查询。 例如: <!-- 模糊查询 --> <select id="findGoodsByLikeName" parameterType="com.xt.vo.Goods" resultMap="goodsResult"> select goods.* from goods where goodsName like '%${goodsName}%' </select> 此处用到了${},而之前我们队参数的传入一直都是#{}。 这里就涉及到了#{}与${}的区别: (1).#{}能够防止SQL注入,而${}不能 在学习过程中,我们发现在一般查询过程中,#{}与${}似乎并没有什么区别,都能查询到我们 想要的结果。 例如: select * from goods where goodsName = #{goodsName} select * from goods where goodsName = '${goodsName}' 但是在动态解析的时候,使用#{}时,会被解析成一个占位符,也就是: select * from goods where goodsName = ? 而${}在动态解析的时候,传入的参数就会被当成一个普通的字符串常量,也就是: select * from goods where goodsName = '哇哈哈' (2).在用${}进行传参的时候,参数变量名必须是parameterType中的属性,并且该属性有对应的 setter()和getter()方法。 在刚才的模糊查询的例子中,我们发现,参数名称为goodsName,而parameterType是Goods实体类 goodsName是Goods实体类中的属性。在这里,如果把goodsName换成name或者其他名称,就会报错。 同样的,加入把parameterType换成其他类型,同样会报相同的错误。因为该类中没有对应的该属性 的setter()和getter()方法。 2.动态SQL (1).if判断 例如: <select id="findGoodsByName" parameterType="com.xt.vo.Goods"> select * from Goods <where> <if test="goodsName!=null"> goodsName = #{goodsName} </if> </where> </select> 属性: test:if判断的内容,并且test中的变量名称必须是parameterType中的属性,且有对应getter() (2).<foreach>循环 例如: <!--一次插入多行--> <insert id="insertToType" parameterType="java.util.List"> insert into tb_type values <foreach collection="list" item="typeName" separator=","> (default,#{typeName}) </foreach> </insert> <!--一次传入多个参数--> <select id="findGoodsByTypeIdList" parameterType="java.lang.Integer" resultMap="findGoodsResult"> select goods.*,tb_type.* from goods inner join tb_type on goods.typeId = tb_type.id and typeId in <foreach collection="list" item="id" open="(" close=")" separator=","> #{id} </foreach> </select> 属性: collection:被循环的类型 item:元素名称 separator:分隔符 #{}中的名称必须与item相同,否则报错 其他动态SQL标签可自行去官网查看。 第五章 MyBatis调用存储过程 在实际开发中,我们常常会去调用存储过程,那么在MyBatis中,如何去调用存储过程呢? (1).对数据库进行增删改,且有返回值时,通常使用Map<Object,Object>做参数 例如: Interface IGoodsTypeMapper中: public void findCntByName(Map<String,Object>) throws Exception; typeMapper.xml中: <select id="findCntByName" parameterType="java.util.Map" resultType="java.lang.Integer" useCache="false" statementType="CALLABLE"> <![CDATA[ { call P_FINDCNTBYNAME( #{type_Name,mode=IN,jdbcType=VARCHAR}, #{cnt,mode=OUT,jdbcType=INTEGER} ) } ]]> </select> 测试类中: IGoodsMapper goodsMapper = session.getMapper(IGoodsMapper.class); Map<String,Object> parameter = new HashMap<String,Object>(); parameter.put("type_name", "葫芦娃4"); goodsMapper.getTypeCntByName(parameter); System.out.println("============>"+parameter.get("cnt")); 在测试过程中的map集合,键的名称应当与typeMapper.xml中#{}的名称一致。即: parameter.put("type_name", "葫芦娃4"); #{type_Name,mode=IN,jdbcType=VARCHAR} 存储过程返回的值,会自动放入Map集合中,并且返回结果的键名称就是typeMapper.xml中的名字一致。 (2).对于查询数据库的存储过程 例如: Interface IGoodsTypeMapper中: public List<GoodsType> getTypeInfo() throws Exception; typeMapper中: <select id="getTypeInfo" resultMap="typeResult" statementType="CALLABLE" useCache="false"> <![CDATA[ { call P_FINDTYPEINFO() } ]]> </select> 测试类中: List<GoodsType> typeList = goodsMapper.getTypeInfo(); for(GoodsType types : typeList){ System.out.println("类型编号:"+types.getId()+"\t"+"类型名称:"+types.getTypeName()); } 第六章 MyBatis缓存机制 MyBatis中包含了一个非常强大的缓存特性,并且配置使用非常方便。缓存能够极大地提升查询效率。 MyBatis中的缓存分为两个级别的缓存,分别为:一级缓存、二级缓存。 1.一级缓存 一级缓存的作用域在sqlSession,默认情况下是开启的。 二级缓存 二级缓存的作用域在nameSpace,需要手动开启和配置。 2.证明一级缓存的存在 IGoodsMapper goodsDao = session.getMapper(IGoodsMapper.class); List<GoodsType> typeList1 = goodsDao.findType(); List<GoodsType> typeList2 = goodsDao.findType(); 执行日志: 11:25:58,099 DEBUG com.xt.dao.IGoodsMapper.findType:145 - ==> Preparing: select tb_type.* from tb_type 11:25:58,187 DEBUG com.xt.dao.IGoodsMapper.findType:145 - ==> Parameters: 11:25:58,245 DEBUG com.xt.dao.IGoodsMapper.findType:145 - <== Total: 28 事实证明,对数据库进行两次查询,但是只发送了一次sql语句。说明第二次的查询结果是从缓存区中拿的。 那么一级缓存在什么情况下会失效呢? (1).在同一个sqlSession执行查询的过程中,进行了增删改操作(强调:同一个sqlSession)。 证明: IGoodsMapper goodsDao = session.getMapper(IGoodsMapper.class); //第一次查询 List<GoodsType> typeList1 = goodsDao.findType(); //增加一条记录 List<String> typeNames = new ArrayList<String>(); typeNames.add("boom"); Integer flag = goodsDao.insertGoodsType(typeNames); session.commit(); //第二次查询 List<GoodsType> typeList2 = goodsDao.findType(); 执行日志: 11:31:39,553 DEBUG com.xt.dao.IGoodsMapper.findType:145 - ==> Preparing: select tb_type.* from tb_type 11:31:39,588 DEBUG com.xt.dao.IGoodsMapper.findType:145 - ==> Parameters: 11:31:39,630 DEBUG com.xt.dao.IGoodsMapper.findType:145 - <== Total: 28 11:31:39,652 DEBUG com.xt.dao.IGoodsMapper.insertGoodsType:145 - ==> Preparing: insert into tb_type values (default,?) 11:31:39,653 DEBUG com.xt.dao.IGoodsMapper.insertGoodsType:145 - ==> Parameters: boom(String) 11:31:39,654 DEBUG com.xt.dao.IGoodsMapper.insertGoodsType:145 - <== Updates: 1 11:31:39,655 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:69 - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5c5a1b69] 11:31:39,660 DEBUG com.xt.dao.IGoodsMapper.findType:145 - ==> Preparing: select tb_type.* from tb_type 11:31:39,661 DEBUG com.xt.dao.IGoodsMapper.findType:145 - ==> Parameters: 11:31:39,667 DEBUG com.xt.dao.IGoodsMapper.findType:145 - <== Total: 29 事实证明:在第一次查询过后,进行了一次增加操作,那么第二次的查询结果并不是从缓存中拿的,而是又进行了一次查询。 为什么要强调同一个sqlSession呢? 刚才的例子中,都是同一个sqlSession,在进行了对数据库的更新后,第二次查询时,并没有从缓存中读取。 现在看下一个例子: SqlSession session1 = sqlSessionFactory.openSession(); SqlSession session2 = sqlSessionFactory.openSession(); //第一个sqlSession1 IGoodsMapper goodsDao1 = session1.getMapper(IGoodsMapper.class); //第二个sqlSession2 IGoodsMapper goodsDao2 = session2.getMapper(IGoodsMapper.class); //sqlSession1第一次查询 List<GoodsType> typeList1 = goodsDao1.findType(); //sqlSession2添加一条数据 List<String> typeNames = new ArrayList<String>(); typeNames.add("boom1"); Integer flag = goodsDao2.insertGoodsType(typeNames); session2.commit(); //sqlSession1第二次查询 List<GoodsType> typeList2 = goodsDao1.findType(); //第一次查询结果 for(GoodsType type1 : typeList1){ System.out.println("类型名称:"+type1.getTypeName()); } //第二次查询结果 for(GoodsType type2 : typeList2){ System.out.println("类型名称:"+type2.getTypeName()); } 查询日志: 21:37:52,260 DEBUG com.xt.dao.IGoodsMapper.findType:145 - ==> Preparing: select tb_type.* from tb_type 21:37:52,298 DEBUG com.xt.dao.IGoodsMapper.findType:145 - ==> Parameters: 21:37:52,355 DEBUG com.xt.dao.IGoodsMapper.findType:145 - <== Total: 31 21:37:52,384 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:136 - Opening JDBC Connection 21:37:52,407 DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource:387 - Created connection 2028371466. 21:37:52,408 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:100 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@78e67e0a] 21:37:52,408 DEBUG com.xt.dao.IGoodsMapper.insertGoodsType:145 - ==> Preparing: insert into tb_type values (default,?) 21:37:52,409 DEBUG com.xt.dao.IGoodsMapper.insertGoodsType:145 - ==> Parameters: boom1(String) 21:37:52,413 DEBUG com.xt.dao.IGoodsMapper.insertGoodsType:145 - <== Updates: 1 21:37:52,414 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:69 - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@78e67e0a] 分析:很显然sqlSession1的第二次查询是从缓存区拿的数据,并没有重新发送sql语句,这样会产生脏读! 这个例子证明了两点: (1).只有在同一个sqlSession中,在查询过程中对数据库进行增删改,一级缓存才会失效。 (2).一级缓存只存在数据库内部共享。 (2).同一个sqlSession,但是查询条件不同。 (3).同一个sqlSession,两次查询期间手动清空了缓存。 3.如何开启二级缓存 (1).在配置文件myBatis.xml中,手动开启。 <settings> <setting name="logImpl" value="LOG4J"/> <!-- 开启二级缓存 --> <setting name="cacheEnabled" value="true"/> </settings> (2).在映射文件中配置二级缓存的属性 <!-- 开启二级缓存 --> <cache eviction="LRU" flushInterval="120000" size="1024" readOnly="true"/> eviction: flushInterval:刷新缓存的时间间隔 size:缓存对象的大小 readOnly:是否只读 4.证明二级缓存生效 SqlSession session = sqlSessionFactory.openSession(); IGoodsMapper goodsDao = session.getMapper(IGoodsMapper.class); List<GoodsType> typeList = goodsDao.findType(); session.close(); SqlSession session2 = sqlSessionFactory.openSession(); IGoodsMapper goodsDao2 = session2.getMapper(IGoodsMapper.class); List<GoodsType> typeList2 = goodsDao2.findType(); 执行日志: 17:27:37,453 DEBUG com.xt.dao.IGoodsMapper.findType:145 - ==> Preparing: select tb_type.* from tb_type 17:27:37,484 DEBUG com.xt.dao.IGoodsMapper.findType:145 - ==> Parameters: 17:27:37,531 DEBUG com.xt.dao.IGoodsMapper.findType:145 - <== Total: 32 17:27:37,531 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:122 - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2b552920] 17:27:37,531 DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction:90 - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@2b552920] 17:27:37,531 DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource:344 - Returned connection 727001376 to pool. 17:27:37,531 DEBUG com.xt.dao.IGoodsMapper:62 - Cache Hit Ratio [com.xt.dao.IGoodsMapper]: 0.5 由上面的执行结果知道,第一次的查询,向数据库发送了sql语句;但是第二个查询,是直接从二级缓存中读取的数据。 缓存命中为:0.5 第七章 MyBatis注解(1) 在之前的学习过行程中,我们都采用的映射文件的方式来对数据库进行操作。MyBatis还未我们提供了注解,方便我们的使用。 废话不多说,直接上菜。 (1).在之前使用映射文件时,在配置文件mybatis.xml中的<mapper></mapper>中是这样的: <mappers> <mapper resource="com/xt/mapper/goodsMapper.xml"></mapper> </mappers> 使用注解后: <mappers> <mapper class="com.xt.mapper.goodsMapper"/> </mappers> (2).在接口中使用注解 例如: /* *查询商品类型信息 */ @Select("select * from goods") @Results( value={ @Result(id=true,property="goodsId",column="goodsId"), @Result(property="goodsName",column="goodsName") } ) public List<Goods> findGoods() throws Exception; /* *删除数据(只有一个参数) */ @Delete("delete from tb_type where id = #{id}") public Integer delete(Integer typeId) throws Exception; /* *删除数据(多个参数) */ @Delete("delete from tb_type where id = #{typeId} or typeName = #{typeName}") public Integer delete2(@Param("typeId") Integer typeId,@Param("typeName") String typeName) throws Exception; /* *添加一条数据(返回主键) */ @Insert("insert into goods values (default,#{goodName},#{price},#{unit},#{date},#{shui},#{type.id})") @Option(useGeneratedKeys=true,keyProperty="id",keyColumn="id") public Integer insertGoodsInfo(Goods goods) throws Exception; 注: 当传递多个参数的时候,需要使用@Param来指明参数,否则会报错:参数找不到。 第八章 MyBatis注解(2) 在之前的学习过程中,我们对数据库进行操作是通过映射文件,然后学习了注解。但是我们会发现,单纯的 注解用来写SQL语句是不够的。所以本章就为大家介绍XXXXProvider的使用方法。 由于MyBatis相对简单,所以直接上代码: (1).GoodsTypeDao.class中的代码: @SelectProvider(type=SqlProvider.class,method="selectType") @Results( value={ @Result(id=true,property="id",column="id"), @Result(property="typeName",column="typeName") } ) public List<GoodsType> findType(@Param("id")Integer id,@Param("name")String typeName) throws Exception; (2).SqlProvider.class中的代码: public String selectType(Map<String,Object> param){ return new SQL() .SELECT("*") .FROM("tb_type") .WHERE("id=#{id} or typeName = #{name}") .toString(); } 解析: type:返回sql语句的类 method:返回sql语句的方法 注: *如果只有一个参数,那么在selectType()中,只需传入一个指定类型的参数就行。在使用的过程中, 在findType()中传入相应参数,该参数就会传入selectType()中,然后赋给sql语句中。 *但是有多个参数时,按照刚才的方法就会报错。因此我们通常会在selectType()中传入一个Map<>集合。 在findType()中,可以使用@Param为传入的参数取别名,这个别名就是Map<>集合中元素的键,在传入 实参的时候的值就是Map<>集合中的键的值。 第九章 没有实体类的查询 在之前的学习过程中,我们对数据库进行操作时,都是利用实体类,比如查询,返回的结果集是List<Goods>。 那么在日常开发中,我们有时候并不会去写实体类,因为那样很麻烦。(当然,我们会从数据库反向生成实体类) 那么如何在没有实体类的情况下,对数据库进行查询呢? 直接看代码: GoodsMapper.class中: @SelectProvider(type=SqlProvider.class,method="searchGoodsInfo") @Results( value={ @Result(id=true,property="goodsId",column="goodsId"), @Result(property="goodsName",column="goodsName") } ) public List<Map<?,?>> searchGoods() throws Exception; 大家可以看到,这里返回的并不是List<Goods>,而是List<Map<?,?>>。 测试类中: IGoodsMapper goodsDao = session.getMapper(IGoodsMapper.class); List<Map<?,?>> goodsList = goodsDao.searchGoods(); for(Map<?,?> map : goodsList){ System.out.println("商品名称:"+map.get("goodsName")); } 由此可以看出:当从数据库查询到数据后,会将列名作为Map集合的键,值作为Map集合的值