#{}:先编译sql语句,再给占位符传值,底层是PreparedStatement实现。可以防止sql注入,比较常用。
${}:先进行sql语句拼接,然后再编译sql语句,底层是Statement实现。存在sql注入现象。只有在需要进行sql语句关键字拼接的情况下才会用到。
需求:根据car_type查询汽车
模块名:mybatis-005-antic
第1步、创建新模块:
第2步、pom.xml文件引入依赖(mybatis依赖、mysql依赖、junit依赖、logback依赖)
4.0.0
com.powernode
mybatis-006-antic
1.0-SNAPSHOT
jar
org.mybatis
mybatis
3.5.10
mysql
mysql-connector-java
8.0.30
junit
junit
4.13.2
test
ch.qos.logback
logback-classic
1.2.11
17
17
UTF-8
第3步、根目录(resources)下创建(CarMapper.xml、jdbc.properties、logback.xml、mybatis-config.xml)
①、CarMapper.xml
②、jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:1997/powernode
jdbc.username=root
jdbc.password=123456
③、logback.xml
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n
④、mybatis-config.xml
第4步、编写接口、pojo类、工具类
①、Carmapper
package com.powernode.mybatis.mapper;
import com.powernode.mybatis.pojo.Car;
import java.util.List;
/**
* @author wuw
* @since 2023-04-07 11:04:43
*/
public interface CarMapper {
/**
* 根据car_num获取Car
* @param carType
* @return
*/
List selectByCarType(String carType);
}
②、Car
package com.powernode.mybatis.pojo;
/**
* @author wuw
* @since 2023-04-07 11:53:16
*/
public class Car {
private Long id;
private String carNum;
private String brand;
private Double guidePrice;
private String produceTime;
private String carType;
// 构造方法
// set get方法
// toString方法
}
③、SqlSessionUtil
package com.powernode.mybatis.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
/**
* @author wuw
* @since 2023-04-04 09:42:28
*/
public class SqlSessionUtil {
private static SqlSessionFactory sqlSessionFactory;
/**
* 类加载时初始化sqlSessionFactory对象
*/
static {
try {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (Exception e) {
e.printStackTrace();
}
}
private static ThreadLocal local = new ThreadLocal<>();
/**
* 每调用一次openSession()可获取一个新的会话,该会话支持自动提交。
*
* @return 新的会话对象
*/
public static SqlSession openSession() {
SqlSession sqlSession = local.get();
if (sqlSession == null) {
sqlSession = sqlSessionFactory.openSession();
local.set(sqlSession);
}
return sqlSession;
}
/**
* 关闭SqlSession对象
* @param sqlSession
*/
public static void close(SqlSession sqlSession){
if (sqlSession != null) {
sqlSession.close();
}
local.remove();
}
}
第5步、编写测试类
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.junit.Test;
import java.util.List;
/**
* @author wuw
* @since 2023-04-07 11:54:23
*/
public class CarMapperTest {
@Test
public void testSelectByCarType(){
CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(CarMapper.class);
List cars = mapper.selectByCarType("燃油车");
cars.forEach(car -> System.out.println(car));
}
}
第6步、运行
通过执行可以清楚的看到,sql语句中是带有 ? 的,这个 ? 就是大家在JDBC中所学的占位符,专门用来接收值的。
把“燃油车”以String类型的值,传递给 ?
这就是 #{},它会先进行sql语句的预编译,然后再给占位符传值
同样的需求,我们使用${}来完成
CarMapper.xml文件修改如下:
再次运行测试程序:
出现以上错误,因为${} 是先进行sql语句的拼接,然后再编译,出现语法错误是正常的,因为燃油车是一个字符串,所以在sql语句中应该添加单引号。
修改一下:
再次运行程序:
通过以上测试,可以看出,对于以上这种需求来说,还是建议使用 #{} 的方式。
原则:能用 #{} 就不用 ${}
当需要进行sql语句关键字拼接的时候。必须使用${}
需求:通过向sql语句中注入asc或desc关键字,来完成数据的升序或降序排列。
第一步、CarMapper接口:
/**
* 查询所有的Car
* @param ascOrDesc asc或desc
* @return
*/
List selectAll(String ascOrDesc);
第二步、CarMapper.xml文件:
第三步、测试程序:
@Test
public void testSelectAll(){
CarMapper mapper = (CarMapper) SqlSessionUtil.openSession().getMapper(CarMapper.class);
List cars = mapper.selectAll("desc");
cars.forEach(car -> System.out.println(car));
}
报以下错误:
报错的原因是sql语句不合法,因为采用这种方式传值,最终sql语句会是这样:
select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType from t_car order by carNum 'desc'
desc是一个关键字,不能带单引号的,所以在进行sql语句关键字拼接的时候,必须使用${}
第4步、使用${} 改造、修改CarMapper.xml
第5步、再次执行测试程序
业务背景:实际开发中,有的表数据量非常庞大,可能会采用分表方式进行存储,比如每天生成一张表,表的名字与日期挂钩,例如:2022年8月1日生成的表:t_user20220108。2000年1月1日生成的表:t_user20000101。此时前端在进行查询的时候会提交一个具体的日期,比如前端提交的日期为:2000年1月1日,那么后端就会根据这个日期动态拼接表名为:t_user20000101。有了这个表名之后,将表名拼接到sql语句当中,返回查询结果。那么大家思考一下,拼接表名到sql语句当中应该使用#{} 还是 ${} 呢?
- 使用#{}会是这样:select * from 't_car'
- 使用${}会是这样:select * from t_car
CarMapper.xml:
CarMapper接口:
/**
* 根据表名查询所有的Car
* @param tableName
* @return
*/
List selectAllByTableName(String tableName);
CarMapperTest.testSelectAllByTableName:
@Test
public void testSelectAllByTableName(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List cars = mapper.selectAllByTableName("t_car");
cars.forEach(car -> System.out.println(car));
}
执行结果:
业务背景:一次删除多条记录。
如果使用mybatis处理,应该使用#{} 还是 ${}
使用#{} :delete from t_user where id in('1,2,3') 执行错误:1292 - Truncated incorrect DOUBLE value: '1,2,3'
使用${} :delete from t_user where id in(1, 2, 3)
第1步、接口CarMapper接口:
/**
* 根据id批量删除
* @param ids
* @return
*/
int deleteBatch(String ids);
}
第2步、CarMapper.xml
delete from t_car where id in(${ids})
第3步、测试类
@Test
public void testDeleteBatch(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
int count = mapper.deleteBatch("1,2");
System.out.println("删除了几条记录:" + count);
SqlSessionUtil.openSession().commit();
}
第4步、运行,update了两条数据
需求:查询比亚迪系列的汽车。【只要品牌brand中含有比亚迪两个字的都查询出来。】
①、使用${}
第1步、CarMapper接口
/**
* 根据品牌进行模糊查询
* @param likeBrank
* @return
*/
List selectLikeByBrand(String likeBrank);
第2步、CarMapper.xml
第3步、编写测试类
@Test
public void testSelectLikeByBrand(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List cars = mapper.selectLikeByBrand("比亚迪");
cars.forEach(car -> System.out.println(car));
}
第4步:运行
首先观察一下CarMapper.xml中的配置信息:
resultType属性用来指定查询结果集的封装类型,这个名字太长,可以起别名吗?可以。
在mybatis-config.xml文件中使用typeAliases标签来起别名,包括两种方式:
mybatis-config.xml文件:
首先要注意typeAliases标签的放置位置,如果不清楚的话,可以看看错误提示信息。
typeAliases标签中的typeAlias可以写多个。
typeAlias:
type属性:指定给哪个类起别名
alias属性:别名。
alias属性不是必须的,如果缺省的话,type属性指定的类型名的简类名作为别名。
alias是大小写不敏感的。也就是说假设alias="Car",再用的时候,可以是CAR,也可以是car,也可以是Car,都行。
如果一个包下的类太多,每个类都要起别名,会导致typeAlias标签配置较多,所以mybatis用提供package的配置方式,只需要指定包名,该包下的所有类都自动起别名,别名就是简类名。并且别名不区分大小写。
mybatis-config.xml文件:
在mybatis-config中配置
CarMapper中映射一下
运行测试代码
SQL映射文件的配置方式包括四种:
- resource:从类路径中加载
- url:从指定的全限定资源路径中加载
- class:使用映射器接口实现类的完全限定类名
- package:将包内的映射器接口实现全部注册为映射器
这种方式是从类路径中加载配置文件,所以这种方式要求SQL映射文件必须放在resources目录下或其子目录下。
这种方式显然使用了绝对路径的方式,这种配置对SQL映射文件存放的位置没有要求,随意。
如果使用这种方式必须满足以下条件:
- SQL映射文件和mapper接口放在同一个目录下。
- SQL映射文件的名字也必须和mapper接口名一致。
将CarMapper.xml文件移动到和mapper接口同一个目录下:
- 在resources目录下新建:com/powernode/mybatis/mapper【这里千万要注意:不能这样新建 com.powernode.mybatis.dao】
- 将CarMapper.xml文件移动到mapper目录下
- 修改mybatis-config.xml文件
如果class较多,可以使用这种package的方式,但前提条件和上一种方式一样。
mybatis-config.xml和SqlMapper.xml文件可以在IDEA中提前创建好模板,以后通过模板创建配置文件。
前提是:主键是自动生成的。
业务背景:一个用户有多个角色。
插入一条新的记录之后,自动生成了主键,而这个主键需要在其他表中使用时。
插入一个用户数据的同时需要给该用户分配角色:需要将生成的用户的id插入到角色表的user_id字段上。
第一种方式:可以先插入用户数据,再写一条查询语句获取id,然后再插入user_id字段。【比较麻烦】
第二种方式:mybatis提供了一种方式更加便捷。
/**
* 获取自动生成的主键
* @param car
*/
void insertUseGeneratedKeys(Car car);
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
@Test
public void testInsertUseGeneratedKeys(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Car car = new Car();
car.setCarNum("5262");
car.setBrand("BYD汉");
car.setGuidePrice(30.3);
car.setProduceTime("2020-10-11");
car.setCarType("新能源");
mapper.insertUseGeneratedKeys(car);
SqlSessionUtil.openSession().commit();
System.out.println("获取的id为:"+" "+car.getId());
}