文章目录
实现对数据库的操作主要依靠一下几个步骤:
- 创建实体类(Entity)
- 创建Mapper XML,编写SQL语句
- 在配置文件中新增mapper配置
- 调用sqlSession方法执行SQL语句
先创建一个实体类
package com.mybatis.entity;
public class Good {
private Integer goodsId;
private String title;
private String subTitle;
private Float originalCost;
private Float currentPrice;
private Float discount;
private Integer isFreeDelivery;
private Integer categoryId;
public Good() {
}
// 省略Get/Set方法和toString方法
......
}
SELECT
结果集查询
- 在Resource资源文件夹中创建XML文件,编辑SQL脚本
<?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">
<mapper namespace="good">
<select id="selectGoods" resultType="com.mybatis.entity.Good">
SELECT * FROM t_goods ORDER BY goods_id DESC LIMIT 10
</select>
</mapper>
namespace指定该XML的空间命名用于标识该文件的唯一性,空间命名有两种写法,一种是短命名,例如selectAll
,使用短命名时如果项目中有相同名称时,就无法使用短名称来调用该xml文件;另一种为全限定名,即com.mypackage.MyMapper.selectAllThings
将被直接用于查找及使用。
select标签的id属性用于标识XML中的唯一语句,resultType即查询结果所对应的实体类
myBatis的便捷就在于将代码与SQL语句完全解耦,并且能够自动封装结果到实体类
- 在mybatis-config.xml中增加配置,添加mapper配置,按照资源路径填写XML的路径,可以在加载配置的时候将xml加入资源中
<mappers>
<mapper resource="mapper/good.xml" />
</mappers>
使用mybatis封装数据时,需要字段名称与实体类的属性名称相同,若字段是使用下划线分割单词时可以在配置文件中加入
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
将下划线命名转为驼峰命名
- 执行SQL语句
SqlSession
提供了几种select方法
<T> T selectOne(String statement)
:查询单一结果<E> List<E> selectList(String statement);
:查询结果集,返回一个List对象
示例:
package com.mybatis;
import com.mybatis.entity.Good;
import com.mybatis.utils.MyBatisUtils;
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 java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
import java.util.List;
public class TestMyBatis {
@Test
public void select() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
List<Good> list = sqlSession.selectList("good.selectGoods");
for (Good good : list)
System.out.println(good);
} catch (Exception e) {
e.printStackTrace();
} finally {
MyBatisUtils.close(sqlSession);
}
}
}
SQL传参
如果想要查找指定数据或某些条件下的数据,这时我们就需要给SQL语句传递参数
<select id="selectById" parameterType="Integer" resultType="com.mybatis.entity.Good">
SELECT * FROM t_goods WHERE goods_id = #{value}
</select>
parameterType
指定传入参数的数据类型,Java中的数据类型都能够通用。在SQL语句中使用#{value}
用于表示传入的参数,在Java中调用该语句时会自动将参数传递进来
@Test
public void testSelectById() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
Good good = sqlSession.selectOne("good.selectById", 746);
System.out.println(good);
} catch (Exception e) {
e.printStackTrace();
} finally {
MyBatisUtils.close(sqlSession);
}
}
在Java中调用<T> T selectOne(String statement, Object parameter)
方法,第二个参数书写需要传递的参数,返回唯一对象
如果有多个参数需要传递给SQL,可以为参数类型指定为java.util.Map
,这样就可以向SQL传递一个map集合,将需要用到的参数放到map集合中,在SQL语句中就可以自动解析map集合将对应键的值赋在指定位置
<select id="selectByPrice" parameterType="java.util.Map" resultType="com.mybatis.entity.Good">
SELECT * FROM t_goods WHERE current_price BETWEEN #{min} AND #{max} LIMIT #{limit}
</select>
@Test
public void testSelectByPrice() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
Map<String, Integer> map = new HashMap<>();
map.put("min", 100);
map.put("max", 500);
map.put("limit", 10);
List<Good> list = sqlSession.selectList("good.selectByPrice", map);
for (Good good : list)
System.out.println(good);
} catch (Exception e) {
e.printStackTrace();
} finally {
MyBatisUtils.close(sqlSession);
}
}
多表联查
在实际开发中,经常会遇到多表联查的情况,这时产生的结果集就不一定能够与实体类相对应。所以针对于这种情况可以将resultType
指定为java.utils.Map
即将查询结果封装为一个Map集合,数据库字段名为键,数据为值
<select id="selectGoodsMap" resultType="java.util.Map">
SELECT tg.*, tc.category_name
FROM t_goods tg
INNER JOIN t_category tc ON tg.category_id = tc.category_id
LIMIT 10
</select>
@Test
public void testSelectGoodsMap() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
List<Map> list = sqlSession.selectList("good.selectGoodsMap");
for (Map map : list)
System.out.println(map);
} catch (Exception e) {
e.printStackTrace();
} finally {
MyBatisUtils.close(sqlSession);
}
}
如此输出的字段是乱序的,并不是按照SQL查询字段顺序,所以如果想要按实际查询顺序则需要使用LinkedHashMap
使用Map结果集来获取结果易于扩展也易于操作,但是却不利于进行编译时检查,对于SQL的查询结果没有对应的实体类来装载
ResultMap结果映射
针对于将结果集生成为map集合的不便,mybatis提供了ResultMap结果集将查询的字段和实体类的属性相互匹配,完成数据的封装。
一般的大型项目都会使用多表联查的方法获取结果,所以单独对应表结构的实体类无法满足对查询结果封装,因此还可以使用DTO类(Data Transfer Object)数据传输对象封装新的实体类
<resultMap id="rmGoods" type="com.mybatis.dto.GoodDTO">
<id property="good.goodsId" column="goods_id"/>
<result property="good.title" column="title"/>
<result property="good.subTitle" column="sub_title"/>
<result property="good.originalCost" column="original_cost"/>
<result property="good.currentPrice" column="current_price"/>
<result property="good.discount" column="discount" />
<result property="good.isFreeDelivery" column="is_free_delivery" />
<result property="good.categoryId" column="category_id"/>
<result property="category.categoryId" column="category_id" />
<result property="category.categoryName" column="categoryName" />
<result property="category.parentId" column="parent_id" />
<result property="category.categoryLevel" column="category_level" />
<result property="category.categoryOrder" column="category_order"/>
<result property="test" column="test"/>
</resultMap>
<select id="selectGoodsDTO" resultMap="rmGoods">
SELECT *, 1 AS test
FROM t_goods tg
INNER JOIN t_category tc ON tg.category_id = tc.category_id
LIMIT 10
</select>
resultMap
标签中id为结果集的唯一标识,type为结果集所对应的实体类。
resultMap
有两个子标签id
和result
,id
标签用于标识数据表的主键字段,其他字段使用result
即可。两个子标签存在两个常用属性property
和column
,property
表示实体类的属性,column
表示数据表的字段名称。
INSERT
现在Mapper XML中创建insert
标签,insert
标签中常用两个属性id
,表示标签的唯一标识,parameterType
表示传入的参数类型
<insert id="insert" parameterType="com.mybatis.entity.Good">
INSERT INTO t_goods (title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
VALUES (#{title}, #{subTitle}, #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
</insert>
使用#{property}
来替代实体类的数据。
selectKey
与useGeneratedKeys
selectKey
与useGeneratedKeys
都是用来获取数据集的主键信息并返回给对应的实体类
selectKey
单独写在insert标签中,使用独立的SQL语句,该标签有以下几个属性
resultType
:结果类型,可以使用Java中的各种数据类型keyProperty
:查询出的结果返回给实体类的属性order
:执行顺序,此处的执行顺序是指是否先完成插入语句,AFTER
为执行插入后执行,BEFORE
为执行插入前执行
<insert id="insert" parameterType="com.mybatis.entity.Good" useGeneratedKeys="">
INSERT INTO t_goods (title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
VALUES (#{title}, #{subTitle}, #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
<selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">
select last_insert_id()
</selectKey>
</insert>
selectKey
适用于所有的关系数据库,应用范围广,但是使用却较为复杂
useGeneratedKeys
是写在insert标签中的作为insert标签的属性出现,默认为false,将其设置为true则开启使用返回主键信息的功能。同时使用的还有keyProperty
和keyColumn
属性表示实体类的属性和对应数据表的字段
<insert id="insert" parameterType="com.mybatis.entity.Good"
useGeneratedKeys="true" keyProperty="goodsId" keyColumn="goods_id">
INSERT INTO t_goods (title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
VALUES (#{title}, #{subTitle}, #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryId})
</insert>
useGeneratedKeys
只能用于拥有“自增主键”的数据库,如MySQL、SQL Server,而Oracle则无法使用该属性,如果需要主键只能使用selectKey
标签
UPDATE
使用update标签书写SQL语句
<update id="update" parameterType="com.mybatis.entity.Good">
UPDATE t_goods SET title = #{title},
sub_title = #{subTitle},
original_cost = #{originalCost},
current_price = #{currentPrice},
discount = #{discount},
is_free_delivery = #{isFreeDelivery},
category_id = #{categoryId}
WHERE goods_id = #{goodsId}
</update>
再调用SqlSession
的update(String statement, Object parameter)
执行SQL脚本,返回值为int
类型的数值,表示影响的行数
@Test
public void testUpdate() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
Good good = sqlSession.selectOne("good.selectById", 2669);
good.setTitle("测试更新");
int update = sqlSession.update("good.update", good);
sqlSession.commit();
} catch (Exception e) {
if (sqlSession != null)
sqlSession.rollback();
e.printStackTrace();
} finally {
MyBatisUtils.close(sqlSession);
}
}
一般修改数据时先获取对应的原始数据,再利用Setter方法进行修改数据
DELETE
使用delete
标签书写删除的SQL语句
<delete id="delete" parameterType="Integer">
DELETE FROM t_goods WHERE goods_id = #{value}
</delete>
再调用SqlSession
的delete(String statement, Object parameter)
方法删除对应的数据,返回值为int
类型的整数表示影响的行数
SQL动态查询
如上文描述的查询、插入、更新、删除操作的条件语句都是写死在SQL语句中,但是在实际开发过程中用户选择的条件是多变的,因此MyBatis提供了动态语句以解决这个问题
<select id="dynamicSelect" parameterType="java.util.Map" resultType="com.mybatis.entity.Good">
SELECT * FROM t_goods
<where>
<if test="categoryId != null">
AND category_id = #{categoryId}
</if>
<if test="currentPrice != null">
AND current_price < #{currentPrice}
</if>
</where>
</select>
where
标签在mybatis中会自动生成where条件,所有的条件都由if
标签进行判断,如果值存在则会添加该条件到where语句中。where
标签会自动检测后边的条件并自动去掉and关键字,若所有的限制条件都不存在则不会再生成where关键字,保证SQL语法的正确性
@Test
public void testDynamicSelect() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSqlSession();
Map map = new HashMap();
// map.put("categoryId", 739);
map.put("currentPrice", 200);
List<Good> list = sqlSession.selectList("good.dynamicSelect", map);
for (Good good : list)
System.out.println(good);
} catch (Exception e) {
e.printStackTrace();
} finally {
MyBatisUtils.close(sqlSession);
}
}
测试用例的输出结果如下:
SQL恶意注入的问题
与JDBC类似,如果在前台的输入框中向后台传递一些带有关键字的信息时,会恶意的破坏SQL语句的基本逻辑,使得实现越权访问信息。
mybatis提供了两种方式进行参数传递
#{value}
:会将参数转换的字符串带入SQL语句中可以避免被SQL注入${value}
:该方法为原文本的传递参数存在SQL注入的风险
${}
方法传递参数主要是用在后台进行对SQL语句的控制,对于前台输入的参数一般还是使用#{}
方法