第一章 如何使用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集合的值