文章目录
项目结构
- 以下为总的项目结构,仅供更好的认识项目的框架,建议根据下面的内容一步步构造项目结构
Mybatis数据查询
Mybatis数据查询步骤
- 创建实体类(entity)
- 创建Mapper XML
- 编写<select>SQL标签
- 开启驼峰命名映射
- 在batis-config.xml文件中增加<mapper>
- SqlSession执行select语句
写代码
- 创建entity实体类包(java→com.imooc.mybatis)
- 创建Goods类(java→com.imooc.mybatis.entity)
- 数据类型与数据库对应的表中的字段保持一致
public class Goods { private Integer goodsId;//商品编号 private String title;//标题 private String subTitle;//子标题 private Float originalCost;//原始价格 private Float currentPrice;//当前价格 private Float discount;//折扣率 private Integer isFreeDelivery;//是否包邮 ,1-包邮 0-不包邮 private Integer categoryId;//分类编号 private List<GoodsDetail> goodsDetails; }
- 生成Get/Set方法
- 创建映射器mappers文件夹(resources→)
- 创建goods.xml(resources→mappers),说明实体类和数据库中的表的对应关系
- 书写<mapper>标签
<?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="goods"> </mapper>
- 书写<select>标签
<!-- namespace: 命名空间,区分不同的sql语句 id: 下面Sql语句的id,用id代表下面的Sql语句 resultType: 说明数据返回的对象--> <mapper namespace="goods"> <select id="selectAll" resultType="com.imooc.mybatis.entity.Goods" useCache="false"> select * from t_goods order by goods_id desc limit 10 </select> </mapper>
- 在mybatis_config.xml中添加<mapper>标签,声明goods.xml
<configuration>
<mappers>
<mapper resource="mappers/goods.xml"/>
</mappers>
</configuration>
- 在MyBatisTestor测试类(test→java→com.imooc.mybatis)中添加测试方法,测试是否查询成功
@Test
public void testSelectAll() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
List<Goods> list = session.selectList("goods.selectAll");
for(Goods g : list){
System.out.println(g.getTitle());
}
}catch (Exception e){
throw e;
}finally {
MyBatisUtils.closeSession(session)
}
}
- 在mybatis_config.xml中添加驼峰命名设置项,开启驼峰命名转换,将数据库中字段转换为驼峰形式,如:goods_id ==> goodsId
<configuration>
<settings>
<!-- goods_id ==> goodsId 驼峰命名转换 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>
SQL传参
向SQL语句中动态传入参数
SQL传参分为单参数传递和多参数传递
- 单参数传递:
- parameterType=“Integer”
- 使用parameterType指定参数的数据类型即可,SQL中#{value}提取参数
- 多参数传递:
- parameterType=“java.util.Map”
- 使用parameterType指定Map接口,SQL中#{key}提取参数
写代码
- 单参数传递,在goods.xml(resources→mappers)中新增<select>查询语句
<mapper>
<!-- 单参数传递,使用parameterType指定参数的数据类型即可,SQL中#{value}提取参数-->
<select id="selectById" parameterType="Integer" resultType="com.imooc.mybatis.entity.Goods">
select * from t_goods where goods_id = #{value}
</select>
</mapper>
- 在MyBatisTestor测试类(test→java→com.imooc.mybatis)中添加测试方法,测试是否查询成功
@Test
public void testSelectById() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
Goods goods = session.selectOne("goods.selectById" , 1603);// ("命名空间 . sql语句的id",传入的具体参数)
System.out.println(goods.getTitle());
}catch (Exception e){
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
- 多参数传递方式,在goods.xml(resources→mappers)中新增<select>查询语句
<mapper>
<!-- 多参数传递时,使用parameterType指定Map接口,SQL中#{key}提取参数 -->
<select id="selectByPriceRange" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
select * from t_goods
where
current_price between #{min} and #{max}
order by current_price
limit 0,#{limt}
</select>
</mapper>
- 在MyBatisTestor测试类(test→java→com.imooc.mybatis)中添加测试方法,测试是否查询成功
@Test
public void testSelectByPriceRange() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
Map param = new HashMap();
param.put("min",100);
param.put("max" , 500);
param.put("limt" , 10);
List<Goods> list = session.selectList("goods.selectByPriceRange", param);
for(Goods g:list){
System.out.println(g.getTitle() + ":" + g.getCurrentPrice());
}
}catch (Exception e){
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
Mybatis获取多表关联查询结果
之前的查询获得的都是单表的查询结果,resultType能承载所有的数据,这种情况在实际项目开发时是比较少的,大部分企业级应用都是多表关联查询产生复杂的结果集,查询字段会横跨很多张表。单独指定某一个实体无法承载所有表的字段,遇到这种情况该怎么做呢?
- 利用resultType="java.util.LinkedHashMap"保存多表关联结果
- MyBatis会将每一条记录包装为LinkedHashMap对象(默认结果为HashMap,获得的结果顺序紊乱,所以强制使用LinkedHashMap)
- key是字段名 value是字段对应的值 , 字段类型根据表结构进行自动判断
- 优点: 易于扩展,易于使用
- 缺点: 太过灵活,无法进行编译时检查
写代码
- 在goods.xml(resources→mappers)中新增<select>查询语句
<mapper>
<!-- 利用LinkedHashMap保存多表关联结果
MyBatis会将每一条记录包装为LinkedHashMap对象
key是字段名 value是字段对应的值 , 字段类型根据表结构进行自动判断
优点: 易于扩展,易于使用
缺点: 太过灵活,无法进行编译时检查
-->
<select id="selectGoodsMap" resultType="java.util.LinkedHashMap" flushCache="true">
select g.* , c.category_name,'1' as test from t_goods g , t_category c
where g.category_id = c.category_id
</select>
</mapper>
- 在MyBatisTestor测试类(test→java→com.imooc.mybatis)中添加测试方法,测试是否查询成功
@Test
public void testSelectGoodsMap() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
List<Map> list = session.selectList("goods.selectGoodsMap");
for(Map map : list){
System.out.println(map);
}
}catch (Exception e){
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
ResultMap结果映射
Map并不是一个最好的解决方案,因为Map虽然灵活,扩展强大,但是在开发的时候体验并不好,但是如果不使用Map,就会衍生出来一个新的问题,如何用对象的方式,保存关联查询的结果呢?这时就可以使用ResultMap结果映射这个配置来解决这个问题。
ResultMap
- ResultMap可以将查询结果映射为复杂类型的Java对象
- ResultMap适用于Java对象保存多表关联结果
- ResultMap支持对象关联查询等高级特性
写代码
思考:在Mybatis获取多表关联查询结果的案例中通过书写selectGoodsMap来实现了保存关联的查询结果,但是使用对象是LinkedHashMap。如果此时希望将这个结果使用Java对象进行保存,该怎么做?
<select id="selectGoodsMap" resultType="java.util.LinkedHashMap" flushCache="true"> select g.* , c.category_name,'1' as test from t_goods g , t_category c where g.category_id = c.category_id </select>
分析:对sql语句进行分析,涉及到两个表,分别是t_goods商品信息表以及t_category分类信息表,同时字段也是来自这两张表。对于这种复合的查询结果使用Goods实体对象无法全部承载,所以需要扩展出Java对象进行保存。
- 创建dto实体类包(java→com.imooc.mybatis)
用于存放数据传输对象,里面的对象对原始的对象进行扩展,用于数据保存和传递。 - 创建GoodsDTO类(java→com.imooc.mybatis.dto)
对于原始Goods实体类进行扩展。
所有的sql字段都可以在 GoodsDTO中找到对应的属性。
//Data Transfer Object--数据传输对象
public class GoodsDTO {
private Goods goods = new Goods();
private Category category = new Category();
private String test;
public Goods getGoods() {
return goods;
}
public void setGoods(Goods goods) {
this.goods = goods;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
- 创建Category实体类(java→com.imooc.mybatis.entity)
public class Category {
private Integer categoryId;
private String categoryName;
private Integer parentId;
private Integer categoryLevel;
private Integer categoryOrder;
}
- 对其添加Getter/Setter方法
问题:如何让Mybatis对其进行对应赋值?
- 在goods.xml(resources→mappers)中新增<select>查询语句,在resultMap进行结果映射
<mapper>
<!--结果映射-->
<resultMap id="rmGoods" type="com.imooc.mybatis.dto.GoodsDTO">
<!--设置主键字段与属性映射-->
<id property="goods.goodsId" column="goods_id"></id>
<!--设置非主键字段与属性映射-->
<result property="goods.title" column="title"></result>
<result property="goods.originalCost" column="original_cost"></result>
<result property="goods.currentPrice" column="current_price"></result>
<result property="goods.discount" column="discount"></result>
<result property="goods.isFreeDelivery" column="is_free_delivery"></result>
<result property="goods.categoryId" column="category_id"></result>
<result property="category.categoryId" column="category_id"></result>
<result property="category.categoryName" column="category_name"></result>
<result property="category.parentId" column="parent_id"></result>
<result property="category.categoryLevel" column="category_level"></result>
<result property="category.categoryOrder" column="category_order"></result>
<result property="test" column="test"/>
</resultMap>
<select id="selectGoodsDTO" resultMap="rmGoods">
select g.* , c.*,'1' as test from t_goods g , t_category c
where g.category_id = c.category_id
</select>
</mapper>
- 在MyBatisTestor测试类(test→java→com.imooc.mybatis)中添加测试方法,测试是否查询成功
@Test
public void testSelectGoodsDTO() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
List<GoodsDTO> list = session.selectList("goods.selectGoodsDTO");
for (GoodsDTO g : list) {
System.out.println(g.getGoods().getTitle());
}
}catch (Exception e){
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
- 执行结果可以看到,GoodsDTO获取到了goods、category和test的所有对象,sql中所有的字段都被承载进来
MyBatis数据插入操作
数据库事务:
是保证数据操作完整性的基础
Mybatis写操作包含三种:
- 插入 - <insert>
- 更新 - <update>
- 删除 - <delete>
写代码
- 在goods.xml(resources→mappers)中新增<insert>插入语句
- parameterType指向实体类(Goods)
- 主键回填:当执行完<insert>的sql语句后,会自动执行
last_insert_id()
这个sql语句,将得到的值回填到Goods的goodId属性中- resultType: 承载以下sql语句的返回值类型
- keyProperty: 需要对应的Goods中的goodsId主键的属性
- order: 执行顺序,执行语句执行完了以后执行这条语句
<mapper>
<!--flushCache="true"在sql执行后强制清空缓存-->
<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods" flushCache="true">
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})
<!--主键回填
resultType: 承载以下sql语句的返回值类型
keyProperty: 需要对应的Goods中的goodsId主键的属性
order: 执行顺序,执行语句执行完了以后执行这条语句-->
<selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">
<!--用于获取当前连接最后产生的id号-->
select last_insert_id()
</selectKey>
</insert>
</mapper>
- 在MyBatisTestor测试类(test→java→com.imooc.mybatis)中添加测试方法,测试是否插入成功
@Test
public void testInsert() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
Goods goods = new Goods();
goods.setTitle("测试商品");
goods.setSubTitle("测试子标题");
goods.setOriginalCost(200f);
goods.setCurrentPrice(100f);
goods.setDiscount(0.5f);
goods.setIsFreeDelivery(1);
goods.setCategoryId(43);
//insert()方法返回值代表本次成功插入的记录总数
int num = session.insert("goods.insert", goods);
session.commit();//提交事务数据
System.out.println(goods.getGoodsId());
}catch (Exception e){
if(session != null){
session.rollback();//回滚事务
}
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
- 执行结果可以看到,goods获取到了数据库回填的goodsId,数据成功插入数据库中
selectKey与useGeneratedKeys的区别
两只都是用于在插入数据时将最新的主键值进行返回
selectKey:
- 需要明确编写获取最新主键的SQL语句
- 适用所有的关系型数据库
useGeneratedKeys:
- 会自动根据驱动生成对应SQL语句
- 只支持“自增主键”类型的数据库
- 用selectKey方式进行回填
<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods">
INSERT INTO SQL语句
<selectKey resultType="Integer" keyProperty="goodId" order="AFTER">
select last_insert_id()
</selectKey>
</insert>
- 用useGeneratedKeys方式进行回填
<insert id="insert"
parameterType="com.imooc.mybatis.entity.Goods"
useGeneratedKeys="true"
keyProperty="goodId"
keyColumn="goods_id">
INSERT INTO SQL语句
</insert>
更新与删除操作
写代码
- 在goods.xml(resources→mappers)中新增<update>更新语句
<mapper>
<update id="update" parameterType="com.imooc.mybatis.entity.Goods">
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>
</mapper>
- 在MyBatisTestor测试类(test→java→com.imooc.mybatis)中添加测试方法,测试是否更新成功
@Test
public void testUpdate() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
Goods goods = session.selectOne("goods.selectById", 739);
goods.setTitle("更新测试商品");
int num = session.update("goods.update" , goods);
session.commit();//提交事务数据
}catch (Exception e){
if(session != null){
session.rollback();//回滚事务
}
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
- 执行结果可以看到,数据更新成功
- 在goods.xml(resources→mappers)中新增<delete>删除语句
<mapper>
<delete id="delete" parameterType="Integer">
delete from t_goods where goods_id = #{value}
</delete>
</mapper>
- 在MyBatisTestor测试类(test→java→com.imooc.mybatis)中添加测试方法,测试是否删除成功
@Test
public void testDelete() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
int num = session.delete("goods.delete" , 740);
session.commit();//提交事务数据
}catch (Exception e){
if(session != null){
session.rollback();//回滚事务
}
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
- 执行结果可以看到,goods_id为740的数据删除成功
预防SQL注入攻击
SQL注入攻击是指攻击者利用SQL漏洞,绕过系统约束,越权获取数据的攻击方式
Mybatis两种传值方式
- ${}文本替换,未经任何处理对SQL文本替换
- #{}预编译传值,使用预编译传值可以预防SQL注入
MyBatis工作流程
应用 | ||
↓ | ||
mybatis-config.xml | → | 全局设置项 环境配置 mapper声明 |
↓ | ||
SqlSession Factory | → build | SqlSession FactoryBuilder |
↓ | ||
SqlSession | → | mapper.xml |
↓ | ||
insert|update|delete|select | → | commit rollback |
↓ | ||
Session Close |