MyBatis(一)——MyBatis简介、MyBatis入门程序
MyBatis(二)——全局配置文件详解
- Mapper接口方法名和Mapper.xml中定义的每个SQL的id相同;
- Mapper接口方法的输入参数类型和mapper.xml中定义的每个sqlparameterType类型相同
- Mapper接口方法的输出参数类型和mapper.xml中定义的sql的resultType的类型相同
- Mapper.xml文件中的namespace,就是接口的类路径。
有两个问题值得注意,第一是接口名必须和映射文件名一致,第二是接口文件和mapper.xml的位置必须放置在同一目录下,只有这样MyBatis才会自动扫描,把接口和映射文件相匹配。否则,就要在全局配置文件中对接口和映射文件分别进行配置。这里我们推荐自动扫描,即把mapper.xml和接口文件放在同一目录下,配置时只需要扫描接口文件就可以了。
话不多说,继MyBatis(一)中的小程序,我们现在来新建一ProductMapper.java接口,此时的目录信息如下:
接着去全局配置文件中进行配置:
<mappers>
<mapper class="com.xx.mapper.ProductMapper"/>
mappers>
我们测试时按照从 映射文件——接口——测试类——控制台 的顺序依次编写代码:
<insert id="insertProductInfo" parameterType="product">
insert into t_product values(
#{id},
#{name},
#{code},
#{price},
#{count},
#{description},
#{status},
#{create_time}
);
insert>
package com.xx.mapper;
import com.xx.entity.Product;
public interface ProductMapper {
// 接口中定义方法,方法名和映射文件中的sql语句id尽量保持一致,该有的参数也要定义出来
public void insertProductInfo(Product product);
}
package com.xx.test;
import java.io.IOException;
import java.util.Date;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import com.xx.entity.Product;
import com.xx.mapper.ProductMapper;
public class MyTest2 {
@Test
public void testInsertProductInfo() throws IOException {
// 1.获取到SqlSessionFactory对象
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsReader("mybatis-config.xml"));
// 2.获取SqlSession对象
SqlSession session = sessionFactory.openSession();
// 3.通过接口的class文件获得mapper
ProductMapper mapper = session.getMapper(ProductMapper.class);
// 4.创建product对象
Product product = new Product();
product.setName("魅族手机");
product.setCode("10009");
product.setPrice(4000.00);
product.setCount(10);
product.setDescription("国产手机品牌");
product.setStatus(1);
product.setCreate_time(new Date());
// 5.mapper执行sql语句
mapper.insertProductInfo(product);
// 6.手动提交事务
session.commit();
// 7.释放资源
session.close();
}
}
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 922872566.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3701eaf6]
==> Preparing: insert into t_product values( ?, ?, ?, ?, ?, ?, ?, ? );
==> Parameters: null, 魅族手机(String), 10009(String), 4000.0(Double), 10(Integer), 国产手机品牌(String), 1(Integer), 2019-04-19 00:36:54.986(Timestamp)
<== Updates: 1
Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3701eaf6]
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3701eaf6]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3701eaf6]
Returned connection 922872566 to pool.
这样,我们就完成了利用接口+mapper.xml文件来操作数据库,其他的增删改查与之类似,不再赘述。
相信你已经发现了,前面我们所讲在sql语句需要多个参数时,我们都干脆直接传递过来一个封装好的javabean对象,这种方法适用于sql语句需要多个参数时,但是当只有两三个参数时,还要封装成对象传过来未免有些麻烦。现在我们学习用注解传递参数来解决这个问题:
比如现在我想通过id来更改价格,那么必不可少的两个参数是id和价格:
<update id="updatePriceById">
update t_product set price=#{price} where id = #{id}
update>
public void updatePriceByID(@Param(value="price")Double price,
@Param(value="id")Integer id);
@Test
public void testUpdateProductById() throws IOException {
// 获取到SqlSessionFactory对象
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsReader("mybatis-config.xml"));
// 获取SqlSession对象
SqlSession session = sessionFactory.openSession();
// 通过接口的class文件获得mapper
ProductMapper mapper = session.getMapper(ProductMapper.class);
// 根据id来更改价格
mapper.updatePriceById(5000.00, 3);
// 手动提交事务
session.commit();
// 释放资源
session.close();
}
==> Preparing: update t_product set price=? where id=?
==> Parameters: 5000.0(Double), 3(Integer)
<== Updates: 1
@Insert : 插入sql , 和xml insert sql语法完全一样
@Select : 查询sql, 和xml select sql语法完全一样
@Update : 更新sql, 和xml update sql语法完全一样
@Delete : 删除sql, 和xml delete sql语法完全一样
@Param : 入参
@Results :结果集合
@Result : 结果
下面我们来学习怎么进行模糊查询,比如我想执行这句SQL语句:
select * from t_product where name like '%米%'
其实模糊查询跟普通的查询并没有太大的区别,值得注意的是’%'与字符的拼接。关于字符串的拼接,我们可以在传参时在测试类中拼接好传过来,也可以在sql语句执行时拼接。
<select id="selectProductByLike" parameterType="String" resultType="product">
select * from t_product where name like #{value}
select>
@Test
public void testSelectProductByLike() throws IOException {
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsReader("mybatis-config.xml"));
SqlSession session = sessionFactory.openSession();
ProductMapper mapper = session.getMapper(ProductMapper.class);
// 直接把拼接好的字符串传过去
List<Product> productByLike = mapper.selectProductByLike("%米%");
session.commit();
session.close();
}
==> Preparing: select * from t_product where name like ?
==> Parameters: %米%(String)
<== Columns: id, name, code, price, count, description, status, create_time
<== Row: 3, 小米手机, 10002, 5000, 20, 国产手机品牌, 1, 2019-04-18 14:04:56.0
<== Total: 1
这种手动拼接的方法不友好,不推荐使用,我们的希望是实现自动拼接:
……
String name = "米";
List<Product> productByLike = mapper.selectProductByLike("%"+name+"%");
……
还有另外的方式,就是在mapper.xml中的sql语句中利用函数自动拼接:
<select id="selectProductByLike" parameterType="String" resultType="product">
select * from t_product where name like concat(concat('%',#{name}),'%')
select>
最后一种方法,利用字符串拼接字符${}
:
select id="selectProductByLike" parameterType="String" resultType="product">
select * from t_product where name like '%${value}%'
</select>
#{} | ${} |
---|---|
相当于占位符 | 相当于字符串拼接符 ,直接把${}的结果和sql语句进行拼接 |
parameterType是基础数据类型以及String时,#{}中可以是任意变量名 | patameterTtye是基础类型以及String时,${}中只能是value |
parameterType是其他引用数据类型时,#{}当中是引用数据类型的属性名 | parameterType是其他引用数据类型时,#{}当中是引用数据类型的属性名 |
#{}解决了sql注入 (PreparedStatement) | ${}存在sql注入 (Statement) |
- 传统的JDBC的方法,在组合SQL语句的时候需要去拼接,稍微不注意就会少少了一个空格,标点符号,都会导致系统错误。Mybatis的动态SQL就是为了解决这种问题而产生的;Mybatis的动态SQL语句值基于OGNL表达式的,方便在SQL语句中实现某些逻辑;可以使用标签组合成灵活的sql语句,提供开发的效率。
- Mybatis的动态SQL标签主要由以下几类:
If语句(简单的条件判断)
Choose(when/otherwise),相当于java语言中的switch,与jstl中choose类似
Trim(对包含的内容加上prefix,或者suffix)
Where(主要是用来简化SQL语句中where条件判断,能智能的处理and/or 不用担心多余的语法导致的错误)
Set(主要用于更新时候)
Foreach(一般使用在mybatis in语句查询时特别有用)
动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分。
查询已上架商品信息如果传参时有name那就根据name来查询,如果没有name那就返回所有已上架商品信息
<select id="selectProductByNameIf" parameterType="product" resultType="product">
select * from t_product where status = 1
<if test="name!=null">
and name = #{name}
if>
select>
public List<Product> selectProductByNameIf(Product product);
@Test
public void selectProductByNameIf() throws IOException {
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsReader("mybatis-config.xml"));
SqlSession session = sessionFactory.openSession();
ProductMapper mapper = session.getMapper(ProductMapper.class);
Product product = new Product();
// name不为空
product.setName("一加手机");
List<Product> productByLike = mapper.selectProductByNameIf(product);
session.commit();
session.close();
}
==> Preparing: select * from t_product where status = 1 and name = ?
==> Parameters: 一加手机(String)
<== Columns: id, name, code, price, count, description, status, create_time
<== Row: 4, 一加手机, 10003, 3999, 15, 国产手机品牌, 1, 2019-04-18 14:06:02.0
<== Total: 1
@Test
public void selectProductByNameIf() throws IOException {
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsReader("mybatis-config.xml"));
SqlSession session = sessionFactory.openSession();
ProductMapper mapper = session.getMapper(ProductMapper.class);
Product product = new Product();
// name不为空
// product.setName("一加手机");
List<Product> products = mapper.selectProductByNameIf(product);
session.commit();
session.close();
}
==> Preparing: select * from t_product where status = 1
==> Parameters:
<== Columns: id, name, code, price, count, description, status, create_time
<== Row: 2, 华为手机, 10001, 4499, 10, 国产手机品牌, 1, 2019-04-18 13:56:19.0
<== Row: 3, 小米手机, 10002, 5000, 20, 国产手机品牌, 1, 2019-04-18 14:04:56.0
<== Row: 4, 一加手机, 10003, 3999, 15, 国产手机品牌, 1, 2019-04-18 14:06:02.0
<== Row: 5, vivo手机, 10006, 4199, 10, 国产手机品牌, 1, 2019-04-18 14:51:03.0
<== Row: 6, 华为手机, 10001, 4499, 10, 国产手机品牌, 1, 2019-04-19 00:35:45.0
<== Row: 7, 魅族手机, 10009, 4000, 10, 国产手机品牌, 1, 2019-04-19 00:36:54.0
<== Total: 6
有些时候,我们不想用到所有的条件语句,而只想从中择其一二。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
查询已上架的商品信息,如果有name属性就用name属性查,没有name属性就根据code属性查,如果name和code都没有,那就返回所有已经上架并且数量大于10的商品信息
<select id="selectByNameAndCodeChoose" parameterType="product" resultType="product">
select * from t_product where status = 1
<choose>
<when test="name!=null">and name = #{name}when>
<when test="code!=code">and code = #{code}when>
<otherwise>and count > 10otherwise>
choose>
select>
动态 SQL 的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。你可以将一个 List 实例或者数组作为参数对象传给 MyBatis,MyBatis 会自动将它包装在一个 Map 中并以名称为键。List 实例将会以"list"作为键,而数组实例的键将是"array"。比如:
根据提供的id列表批量删除数据
<delete id="deleteProductByIdList">
delete from t_product where id in
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
foreach>
delete>
public void deleteProductByIdList(List<Integer> list);
@Test
public void deleteProductByIdList() throws IOException {
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsReader("mybatis-config.xml"));
SqlSession session = sessionFactory.openSession();
ProductMapper mapper = session.getMapper(ProductMapper.class);
List<Integer> list = new ArrayList<Integer>();
list.add(2);
list.add(3);
list.add(4);
System.out.println(list);
mapper.deleteProductByIdList(list);
session.commit();
session.close();
}
==> Preparing: delete from t_product where id in ( ? , ? , ? )
==> Parameters: 2(Integer), 3(Integer), 4(Integer)
<== Updates: 3
先来看一个 if 动态语句:
<select id="selectByNameOrCode" parameterType="product" resultType="product">
select * from t_product where
<if test="status!=null">
status= 1
if>
<if test="name!=null">
and name = #{name}
if>
select>
有一种情况是 status == null 而且 name != null 那么sql语句就变成了:
select * from t_product where and name = #{name}
这显然是不对的。
另一种情况:status == null 而且 name == null 那么sql语句就变成了:
select * from t_product where
这显然也是不对的。怎么来解决where
和and
的问题呢?
<select id="selectByNameOrCode" parameterType="product" resultType="product">
select * from t_product
<where>
<if test="status!=null">
status= 1
if>
<if test="name!=null">
and name = #{name}
if>
where>
select>
这种写法就解决了这个问题,where 元素知道只有在一个以上的 if 条件有值的情况下才去插入"WHERE"子句。而且,若最后的内容是"AND"或"OR"开头的,where 元素也会在适当的时候将他们去除。
与where类似的用法是set:
<update id="updateByPriceOrStatus">
update t_product
<set>
<if test="price!=null">price = #{price},if>
<if test="status!=null">status = #{status}if>
set>
where id = #{id}
update>
set 元素知道只有在一个以上的 if 条件有值的情况下才去插入"SET"子句。set元素也会在适当的时候将’,'去除。
我们还是可以通过自定义 trim 元素来定制我们想要的功能,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
……
trim>
注意:prefixOverrides中AND后面的空格也是必要的。
Sql块就是在mapper.xml中可以被共享的一段sql片段。用它可以代替在映射文件中大量出现的sql语句片段,避免代码的重复:
sql片段
<sql id="selectBase">
select * from t_product
sql>
<select id="selectById" parameterType="int" resultType="product">
<include ref
id="selectBase"/> where id = #{id}
select>
Mybatis本身有分页查询,但是并不是真正的分页查询,它是把数据查出来放在内存里面,你想要什么就给你什么。
我们使用Mybatis实现分页查询的时候,是要实现真分页查询,就是要用sql语句来实现分页查询。MySQL和Oracle两种数据库的实现方法是不一样的。
Mysql:select * from table limit N , M; 其中:N表示从第几页开始,M表示每页显示的条数。比如:数据库中有30条数据,要求每页显示10条,显示第2页的所有数据。
SQL语句就可以写成:Limit 10 , 20
Oracle实现分页查询:采用伪列ROWNUM
我们分别演示一下:
<select id="selectProductLimit" resultType="product">
select * from t_product limit #{start},#{size}
select>
public List<Product> selectProductLimit(
@Param(value="start")Integer start,@Param(value="size")Integer size);
@Test
public void selectProductLimit() throws IOException {
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsReader("mybatis-config.xml"));
SqlSession session = sessionFactory.openSession();
ProductMapper mapper = session.getMapper(ProductMapper.class);
// 从第一条数据开始,每页显示三条数据
List<Product> productLimit = mapper.selectProductLimit(0, 3);
session.commit();
session.close();
}
==> Preparing: select * from t_product limit ?,?
==> Parameters: 0(Integer), 3(Integer)
<== Columns: id, name, code, price, count, description, status, create_time
<== Row: 2, 华为手机, 10001, 4499, 10, 国产手机品牌, 1, 2019-04-18 13:56:19.0
<== Row: 3, 小米手机, 10002, 5000, 20, 国产手机品牌, 1, 2019-04-18 14:04:56.0
<== Row: 4, 一加手机, 10003, 3999, 15, 国产手机品牌, 1, 2019-04-18 14:06:02.0
<== Total: 3
<select id="selectProductListForPageRowBounds"
parameterType="org.apache.ibatis.session.RowBounds"
resultType="product">
select * from t_product
select>
public List<Product> selectProductListForPageRowBounds(RowBounds rowBounds);
@Test
public void selectProductListForPageRowBounds() throws IOException {
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsReader("mybatis-config.xml"));
SqlSession session = sessionFactory.openSession();
ProductMapper mapper = session.getMapper(ProductMapper.class);
// 从第一条数据开始,每页显示三条数据
List<Product> productLimit = mapper.selectProductListForPageRowBounds(new RowBounds(0,3));
session.commit();
session.close();
}
==> Preparing: select * from t_product
==> Parameters:
<== Columns: id, name, code, price, count, description, status, create_time
<== Row: 2, 华为手机, 10001, 4499, 10, 国产手机品牌, 1, 2019-04-18 13:56:19.0
<== Row: 3, 小米手机, 10002, 5000, 20, 国产手机品牌, 1, 2019-04-18 14:04:56.0
<== Row: 4, 一加手机, 10003, 3999, 15, 国产手机品牌, 1, 2019-04-18 14:06:02.0
很显然,我们把SQL语句写在xml文件中,有些字符是不允许的:比如说小于号<
和与AND&
是不能正常来使用的。
“<” 会产生错误,因为解析器会把该字符解释为新元素的开始。
“&” 也会产生错误,因为解析器会把该字符解释为字符实体的开始。
类似的字符还有<=
等字符。
这些都是属于特殊字符,我们在编程时需要对特殊字符进行处理,否则会导致程序不能正常运行。
有两种处理方法:
< |
<= |
> |
>= |
& |
' |
" |
---|---|---|---|---|---|---|
< |
<= |
> |
>= |
& |
' |
" |
举例:
SELECT * FROM emp WHERE salary < 10000;
就可以改写为:
SELECT * FROM emp WHERE salary < 10000;
开始,由 ]]>
结束。
CDATA 部分中的所有内容都会被解析器忽略。
举例:
select * from t_product where id > #{id}
改写为:
elect * from t_product where id <![CDATA[ < ]]> #{id}
我们已经见到了用纯xml文件和xml文件+接口的方式编写mapper,现在来看接口+注解的方式操作数据库。这里我们只介绍最简单的实现过程,注解的高级用法不做介绍。
package com.xx.mapper;
import org.apache.ibatis.annotations.Insert;
import com.xx.entity.Product;
public interface ProducuMapper2 {
// 在这里用接口加注解的方式操作数据库
@Insert("insert into product values (null,#{name},#{code},#{price},#{count},#{description},#{status},#{creat_time})")
public void insertProduct2(Product product);
}
@Test
public void insertProduct2() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sf.openSession();
Product product = new Product();
product.setName("康师傅矿泉水");
product.setCode("001");
product.setPrice(2.0);
product.setCount(10);
product.setDescription("饮用矿泉水");
product.setStatus(1);
product.setCreat_time(new Date());
ProducuMapper2 mapper = sqlSession.getMapper(ProducuMapper2.class);
mapper.insertProduct2(product);
sqlSession.commit();
sqlSession.close();
System.out.println("插入成功!");
}
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 1316864772.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4e7dc304]
==> Preparing: insert into product values (null,?,?,?,?,?,?,?)
==> Parameters: 康师傅矿泉水(String), 001(String), 2.0(Double), 10(Integer), 饮用矿泉水(String), 1(Integer), 2019-04-21 21:28:53.185(Timestamp)
<== Updates: 1
Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4e7dc304]
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4e7dc304]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4e7dc304]
Returned connection 1316864772 to pool.
插入成功!
其他的增删改查与之类似,使用注解@Insert、@Update、@Delete、@Select即可。
在全局配置文件中的
中加入如下配置就可以支持驼峰命名:
<setting name="mapUnderscoreToCamelCase" value="true" />
这样就可以实现自动映射,比如create_time自动映射crateTime。