mybatis常见面试题链接:2023年-Mybatis常见面试题_是Smoky呢的博客-CSDN博客
MVC架构模式和三层架构
在说Mybatis之前,需要知道MVC架构模式和三层架构的这种思想
MVC架构模式
M:Model,数据层。都是和数据相关,比如实体类、从数据库中返回的数据等。
V:View,视图层。在页面中,动态的展示给用户。比如JSP。
C:Controller,控制层。可以理解为它是一个指挥人,从前端接收到参数后,分别使唤M和V完成业务功能。
三层架构
表现层:用户接收用户请求及其参数,将页面展示给用户,调用业务层。
业务层:将标签层传递的参数进行处理,根据不同的业务需求,将不同的结果返回给表现层,调用数据持久层
之间的关系
一、Mybatis概述
1. 为什么要使用Mybatis?
// ...... // sql语句写死在java程序中 String sql = "insert into t_user(id,idCard,username,password,birth,gender,email,city,street,zipcode,phone,grade)values(?,?,?,?,?,?,?,?,?,?,?,?)"; PreparedStatement ps = conn.prepareStatement(sql); // 繁琐的赋值:思考⼀下,这种有规律的代码能不能通过反射机制来做⾃动化。 ps.setString(1, "1"); ps.setString(2, "123456789"); ps.setString(3, "zhangsan"); ps.setString(4, "123456"); ps.setString(5, "1980-10-11"); ps.setString(6, "男"); ps.setString(7, "zhangsan@126.com"); ps.setString(8, "北京"); ps.setString(9, "⼤兴区凉⽔河⼆街"); ps.setString(10, "1000000"); ps.setString(11, "16398574152"); ps.setString(12, "A"); // 执⾏SQL int count = ps.executeUpdate(); // ......// ...... // sql语句写死在java程序中 String sql = "select id,idCard,username,password,birth,gender,email,city,s treet,zipcode,phone,grade from t_user"; PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery(); List<User> userList = new ArrayList<>(); // 思考以下循环中的所有代码是否可以使⽤反射进⾏⾃动化封装。 while(rs.next()){// 获取数据String id = rs.getString("id");String idCard = rs.getString("idCard");String username = rs.getString("username");String password = rs.getString("password");String birth = rs.getString("birth");String gender = rs.getString("gender");String email = rs.getString("email");String city = rs.getString("city");String street = rs.getString("street");String zipcode = rs.getString("zipcode");String phone = rs.getString("phone");String grade = rs.getString("grade");// 创建对象User user = new User();// 给对象属性赋值user.setId(id);user.setIdCard(idCard);user.setUsername(username);user.setPassword(password);user.setBirth(birth);user.setGender(gender);user.setEmail(email);user.setCity(city);user.setStreet(street);user.setZipcode(zipcode);user.setPhone(phone);user.setGrade(grade);// 添加到集合userList.add(user); } // ......
- SQL语句直接写死在Java程序中,相同的语句不能够重复利用。不灵活:当数据库中的表设计进行改变时,那我们就需要该SQL,这样就相当于在修改Java程序,违背了OCP原则(对扩展开放,对修改关闭)
- 给?传值是频繁的,一个?就是一个setxxx语句
- 将结果集封装成Java对象是频繁的,一个字段就是一个getxxx语句
这些Mybatis都能实现,所以需要使用,而且Mybatis是一个框架,属于数据持久层。只需要在框架的基础上做二次开发即可。
2. 什么是Mybatis
- 是一款非常优秀的持久层框架
- 支持自定义SQL(手动编写SQL语句),支持存储过程与高级映射(一对一,一对多,多对多)
- 免除了几乎所有的JDBC代码以及设置参数和封装结果集的工作
- 可以通过简单XML文件或者注解的方式来编写SQL语句
- 是一个ORM框架
2.1 什么是ORM
O:Object,对象。代表的是JVM中所加载的JAVA对象
R:Relational,关系型数据库。指的是Mysql这些数据库
M:Mapping,映射。可以将JVM中的JAVA对象映射成数据库表中的一条记录;或者将数据库中的一条记录映射成JVM中的JAVA对象
二、Mybatis入门程序
1. 表设计
- id:主键(自增)【bigint】,自然主键 和业务无关
- car_num:汽车编号【varchar】
- brand:品牌【varchar】
- guide_price:价钱【decimal】
- producer_time:生产日期【char,只需要年月日】
- car_type:汽车类型(燃油车,电动车,新能源)【varchar】
2. 在POM.XML中添加Mybatis依赖和Mysql驱动
<!--mybatis依赖--> <dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.10</version> </dependency> <!--mysql驱动依赖--> <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.8</version> </dependency>
3. 在resources根目录创建mybatis-conf.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><environments default="development"><environment id="development"><!--先放着--><transactionManager type="JDBC"/><dataSource type="POOLED"><!--数据源 将以下value修改为自己的--><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/jiuxiao"/><property name="username" value="root"/><property name="password" value="1234"/></dataSource></environment></environments><mappers><!--先放着--><mapper resource=""/></mappers> </configuration>
- 这是Mybatis的核心配置文件,文件名不一定叫做mybatis-conf.xml,自定义 随便。
- 这个核心配置文件可以放在任意位置(后面说为什么),这里放在resources根下,相当于放在了类的根路径下(类加载器从这里开始加载)
4. 在resources根目录下创建CarMapper.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="jiuxao"> <!--namespace先随便写--><!--<insert id=""></insert>--> <!--增--><!--<delete id=""></delete>--> <!--删--><!--<update id=""></update>--> <!--该--><!--<select id=""></select>--> <!--查--><insert id="insertCar">insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,'1003','丰田',11.10,'2021-05-11','燃油车')</insert></mapper>
- 标签中的id值必须唯一
- inser ...SQL语句结尾";"可写可不写
- 文件名不一定叫做CarMapper.xml,自定义 随便
- 存放的位置随便(后面说为什么),这里放在resources根下,相当于放在了类的根路径下(类加载器从这里开始加载)
5. 将CarMapper.xml在mybatis-conf.xml中注册
<mappers><!--resource查找的是根路径--><mapper resource="CarMapper.xml"/> </mappers>
如果将这个配置文件放在了resources中的某一个文件夹下,那就是xxx/CarMapper.xml(xxx代表文件名)
6. 编写测试类
我们前面有了一个叫做mybatis-conf.xml的配置文件,读取到这个文件是为了创建SqlSessionFactory
- 读:通过Resources这个工具类进行读取(Mybatis提供)
- 创建:通过SqlSessionFactoryBuilder来创建(这个对象直接new即可)
- 是什么:一个创建SqlSession工厂类
- 有什么用:专门创建SqlSession
Mybatis执行SQL语句就是通过这个SqlSession对象
我们都知道session是一次会话,那么SqlSession是个什么东西?
/** * @author 酒萧 * @version 1.0 * @since 1.0 */ public class MybatisTest {public static void main(String[] args) throws IOException {// SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession --> Sql// 创建SqlSessionFactoryBuilder对象SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();// 获取SqlSessionFactoryInputStream is = Resources.getResourceAsStream("mybatis-conf.xml"); // mybatis中提供的类:ResourcesSqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is); // 参数为一个输入流,也就是读取mybatis-conf.xml// 获取SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession(); //// 执行sqlint count = sqlSession.insert("insertCar");// 参数就是CarMapper.xml中的idSystem.out.println("成功增加"+count+"条数据");} }
6.1 改造
/** * @author 酒萧 * @version 1.0 * @since 1.0 */ public class MybatisTest {public static void main(String[] args) throws IOException {// SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession --> Sql// 创建SqlSessionFactoryBuilder对象SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();// 获取SqlSessionFactoryInputStream is = Resources.getResourceAsStream("mybatis-conf.xml"); // mybatis中提供的类:ResourcesSqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is); // 参数为一个输入流,也就是读取mybatis-conf.xml// 获取SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession(); //// 执行sqlint count = sqlSession.insert("insertCar");// 参数就是CarMapper.xml中的idSystem.out.println("成功增加"+count+"条数据");// 提交事务sqlSession.commit();// 最后记得释放资源sqlSession.close();} }
6.2 解决之前问题
哪些问题:mybatis-conf.xml和CarMapper.xml配置文件位置为什么可以随便放?文件名为什么随便起?
我们在使用resources读取mybatis-con.xml文件后会返回一个输入流。在构建SqlSessionFactory时通过这个输入流对象获取。那么也就说明只要给一个输入流对象就能够返回SqlSessionFactory对象。
测试:使用FileInputStream来去读取这个文件,修改mybatis-cong.xml配置文件所在文件和文件名
// 获取SqlSessionFactory InputStream is = new FileInputStream(new File("E:\\var\\abc.xml")); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is); // 参数为一个输入流,也就是读取mybatis-conf.xml
我们在mybatis-cong.xml中注册CarMapper.xml的时候是通过一个resource属性去指向我们的CarMapper.xml文件,除了resource属性,还有一个url属性,指向的是某个文件的绝对路径
测试:使用url属性读取CarMapper.xml文件,修改CarMapper.xml配置文件名和存放路径
注意:使用url指向绝对路径的时候前面需要加上file:///然后再是文件的绝对路径
<mappers><!--url查找的是文件的绝对路径--><mapper url="file:///E:/BaiduNetdiskDownload/aaa.xml"/> </mappers>
/** * @author 酒萧 * @version 1.0 * @since 1.0 */ public class MybatisTest {public static void main(String[] args) throws IOException {// SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession --> Sql// 创建SqlSessionFactoryBuilder对象SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();// 获取SqlSessionFactoryInputStream is = Resources.getResourceAsStream("mybatis-conf.xml"); // mybatis中提供的类:ResourcesSqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is); // 参数为一个输入流,也就是读取mybatis-conf.xml// 获取SqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();// 执行sqlint count = sqlSession.insert("insertCar");// 参数就是CarMapper.xml中的idSystem.out.println("成功增加"+count+"条数据");sqlSession.commit();// 最后记得释放资源sqlSession.close();} }
三、源码跟踪【如何读取mybatis-con.xml和事务管理器】
1. 如何读取mybatis-conf.xml核心配置文件
1.2 进入这个方法后,里面只调用了另外一个重载方法,直接看重载方法
- ClassLoader:指的是类加载器,因为我们没有传入,所以为null
- resource:传入的mybatis-conf.xml核心配置文件
1.3 这个classLoaderWrapper调用了它直接的getResourceAsStream(),并将参数传入,先看classLoaderWrapper这个对象(点击去后里面调用构造方法,没有看意义,直接看构造方法)
- ClassLoader.getSystemClassLoader()这行代码获取的是系统类加载器
- 将系统类加载器赋值给systemClassLoader
- 那么上面classLoaderWrapper的值就是系统类加载器
- 故然我们在程序中也可以直接通过这个类加载器去读取我们的配置文件(等会演示)
1.4 classLoaderWrapper.getResourceAsStream()中首先调用getClassLoaders()
目的:为了在不同的环境下,使用不同的类加载器加载JAVA对象
- 首先遍历那五个类加载器
- 然后调用getResourceAsStream(),这是JDK中的方法,没必要继续往下看了。
在调用这个方法时将我们的mybatis-cong.xml文件传入进去
- 如果能够读取到这个文件,那么这个InputStream对象不为null,并将这个对象返回
- 如果读取不到,那么这个程序返回null值
1.6 classLoaderWrapper.getResourceAsStream()方法结束
- 判断的对象就是上面的InputStream
- 如果该对象不为null,那么直接返回该对象
- 如果该对象为null,抛出异常
// 获取SqlSessionFactory InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-conf.xml"); // 使用系统类加器去读取配置文件 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is); // 参数为一个输入流,也就是读取mybatis-conf.xml
2. 事务管理器
我们在上面能够看到如果我们不手动提交事物,那么最终数据将不会影响到表中,这是为什么?
在mybatis-conf.xml文件中有一个<transacationManager>标签,里面有一个type属性,对应的值有两个,一个是JDBC,另一个是MANAGED,官网中也有说明:
2.1 JDBC
当type属性设置为JDBC的时候,Mybatis会亲自接管事务,所以在程序中开启事务和提交事务都需要手动完成。
在我们获取SqlSession时,底层实际上执行的是conn.setAutoCommit(false)
SqlSession sqlSession = sqlSessionFactory.openSession(); // 底层相当于conn.setAutoCommit(false);
2.1.1 点进这个openSession(),里面调用openSessionFromDataSource(),并传入一个false值(关键所在)
2.1.2 进入openSessionFromDataSource()
- 首先创建一个初始值为null的Transcation,这个就是事务管理器
- 怎么赋值?
2.1.3 这个方法肯定会返回一个Transcation对象
2.1.4 这个构造方法里面完成了一写初始化操作:只需要关注autoCommit
- 通过传入的值就行判断,如果判断结果为true就开启事务
- 判断结果为false则没有事务
因为底层执行了conn.setAutoCommit(false),所以我们必须在程序中提交事务
如果我们在获取SqlSession的时候传入true会怎样?
判断:true != true执行结果为false,不会进判断,也就代表并没有开启事物,所以在程序中就算不提交也能增加成功(不演示,这句话不会错!)
2.2 MANAGED
如果type属性设置为MANAGED,那么Mybatis将不会接管事务,由其他容器进行管理
如果我们这时没有其他的容器进行管理,那么就证明该事务没有开启,也就代表没有事务
<transactionManager type="MANAGED"/>
// 获取SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); // 底层相当于conn.setAutoCommit(false);// 执行sql int count = sqlSession.insert("insertCar");// 参数就是xxxMapper.xml中的idSystem.out.println("成功增加"+count+"条数据");// sqlSession.commit(); // 底层相当于conn.commit(); 这里不需要提交 因为没有事务
四、简化代码
1. 重复的代码
// 获取SqlSession SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build("mybatis-conf.xml"); SqlSession sqlSession = factory.openSession(); // 释放资源 sqlSession.close();
2. 封装
2.1 第一种方式:抽取工具类
/*** SqlSession工具类* 用来创建SqlSession*/ public class SqlSessionUtil {// 将构造方法私有化,防止别人new对象private SqlSessionUtil(){}/*在这个工具类第一次加载的时候,就去读取配置文件,创建SqlSessionFactory一个SqlSessionFactory对应一个environment*/static SqlSessionFactory sqlSessionFactory = null;static {try {sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-conf.xml"));} catch (IOException e) {throw new RuntimeException(e);}}/*** 获取SqlSession* @return SqlSession*/public static SqlSession openSession(){return sqlSessionFactory.openSession();} }
@Test public void testUtil() {// 通过工具类获取SqlSessionSqlSession sqlSession = SqlSessionUtil.openSession();int count = sqlSession.insert("insertCar");if(count>0){System.out.println("增加成功");}else{System.out.println("增加失败");}sqlSession.commit();// 释放资源sqlSession.close(); }
2.2 第二种方式:借助junit中的注解
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope> </dependency>
- @Before:在每个@Test所注解的方法执行之前执行
- @Aftre:在每个@Test所注解的方法执行之后执行
public class MybatisTest {SqlSession sqlSession;@Beforepublic void before(){// 在每个@Test执行前创建SqlSessiontry {SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-conf.xml"));sqlSession = sqlSessionFactory.openSession();} catch (IOException e) {throw new RuntimeException(e);}}@Afterpublic void after(){// 在每个@Test执行后释放资源 不需要提交事物,因为有查询.sqlSession.close();} }
@Test public void testAnnotation(){int count = sqlSession.insert("insertCar");if(count>0){System.out.println("增加成功");}else{System.out.println("增加失败");}sqlSession.commit(); }
3. 日志
该属性应用在"mybatis-conf.xml"核心配置文件settings标签中
- LOG4J
- SLF4J
- LOG4J2
- STDOUT_LOGGING
- ...
其中STDOUT_LOGGING是Mybatis内置的一个日志组件,直接用就行
如果想用其他日志,需要导入对应的依赖和配置文件,然后将value修改即可(不演示)
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><settings><setting name="logImpl" value="STDOUT_LOGGING"/></settings> </configuration>
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 1845904670. ==> Preparing: insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,'1003','丰田',11.10,'2021-05-11','燃油车') ==> Parameters: <== Updates: 1 增加成功 Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6e06451e] Returned connection 1845904670 to pool.
五、使用Mybatis进行数据库的CRUD
在进行编写的时候注意一个问题:之前在CarMapper.xml文件中,是如何将值写入到数据库的?
<insert id="insertCar"> insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,'1003','丰田',11.10,'2021-05-11','燃油车') </insert>
- JDBC中占位符为?,既然JDBC中都有站位符,那么Mybatis中肯定也有。
- Mybatis中的占位符为#{},使用这个占位符底层实际上使用的是PreparedStatement对象
<insert id="insertCar"> insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{},#{},#{},#{},#{}) </insert>
1. 增加
1.1 使用Map的方式进行传值
// 使用Map进行传参 Map<String,Object> map = new HashMap<>(); map.put("k1","1005"); map.put("k2","法拉利"); map.put("k3",100.0); map.put("k4","2000-05-23"); map.put("k5","电动车");
@Test public void testInsert() { SqlSession sqlSession = SqlSessionUtil.openSession(); // 使用Map进行传参 Map<String,Object> map = new HashMap<>(); map.put("k1","1005"); map.put("k2","法拉利"); map.put("k3",100.0); map.put("k4","2000-05-23"); map.put("k5","电动车");/*增加:insert参数:1.配置文件中的id值2.需要传入的参数*/int count = sqlSession.insert("insertCar", map); // 将map封装好的数据传入if (count>0) {System.out.println("增加成功");}else{System.out.println("增加失败");}sqlSession.commit();sqlSession.close(); }
<insert id="insertCar">insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{k1},#{k2},#{k3},#{k4},#{k5}) </insert>
==> Preparing: insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,?,?,?,?,?)
==> Parameters: 1005(String), 法拉利(String), 100.0(Double), 2000-05-23(String), 电动车(String)
<== Updates: 1
增加成功查看日志可以发现values()中使用的?占位符,足以证明#{}底层使用的就是PreparedStatement对象
<insert id="insertCar">insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{abc},#{k2},#{k3},#{k4},#{k5}) </insert>
虽然程序可以正常运行,但是#{abc}的写法导致无法获取到Map集合中的数据,最终导致将null插入表中
虽然使用k1,k2,k3...可以插入,但是可读性太差,为了增强可读性。最好见名知意:
Map<String,Object> map = new HashMap<>(); map.put("carNum","1005"); map.put("brand","法拉利"); map.put("guidePrice",100.0); map.put("produceTime","2000-05-23"); map.put("carType","电动车");
<insert id="insertCar">insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType}) </insert>
1.2 使用POJO的方式进行传值
/** * Car * @author 酒萧 * @version 1.0 * @since 1.0 */ public class Car {private Long id; // 使用包装类的原因:从数据库查询的数据可能为null,如果是long = null会报错private String carNum;private String brand;private Double guidePrice;private String produceTime;private String carType;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getCarNum() {return carNum;}public void setCarNum(String carNum) {this.carNum = carNum;}public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public Double getGuidePrice() {return guidePrice;}public void setGuidePrice(Double guidePrice) {this.guidePrice = guidePrice;}public String getProduceTime() {return produceTime;}public void setProduceTime(String produceTime) {this.produceTime = produceTime;}public String getCarType() {return carType;}public void setCarType(String carType) {this.carType = carType;}@Overridepublic String toString() {return "Car{" +"id=" + id +", carNum='" + carNum + '\'' +", brand='" + brand + '\'' +", guidePrice=" + guidePrice +", produceTime='" + produceTime + '\'' +", carType='" + carType + '\'' +'}';} }
@Test public void testInsetPOJO() {// 获取sqlSessionSqlSession sqlSession = SqlSessionUtil.openSession();// 封装CarCar car = new Car();car.setCarNum("1006");car.setBrand("丰田");car.setGuidePrice(15.0);car.setProduceTime("2022-02-22");car.setCarType("燃油车");// 传入car,进行增加int count = sqlSession.insert("insertCarOfPOJO", car);if (count>0) {System.out.println("增加成功");}else{System.out.println("增加失败");}sqlSession.commit();sqlSession.close(); }
<insert id="insertCarOfPOJO">insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType}) </insert>
==> Preparing: insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,?,?,?,?,?)
==> Parameters: 1006(String), 丰田(String), 15.0(Double), 2022-02-22(String), 燃油车(String)
<== Updates: 1
增加成功在CarMapper.xml文件中#{}里面写的是属性,如果写成其他的会怎样?
<insert id="insertCarOfPOJO">insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{abc},#{brand},#{guidePrice},#{produceTime},#{carType}) </insert>
1.3说明:
- 如果我们选择Map集合的方式进行传参,那么#{}里面需要写map集合的key,如果写入的没和key对应,并不会报错,而是返回null值,并插入数据库中
- 如果我们选择POJO的方式进行传参,那么#{}里面需要写getXxx()-->Xxx-->xxx,也就是getter方法去掉get然后将首字母小写剩余的部分,例如:getName()-->Name-->name,所以#{}应该传入name
简述一下传入POJO的取值流程 :这里拿#{name}举例(需要有点反射知识)
首先获取对象的字节码-->拿到name-->Name-->getName()-->根据getName()去获取Method对象-->(如果这里获取不到Method对象,后面的不会执行了,直接抛出异常,也就是在xxx类中找不到这个属性的getter方法)使用invoke()调用该方法-->拿到值插入到SQL语句中-->添加到数据库中
2. 删除
<delete id="deleteCarById">delete from t_car where id = #{id} </delete>
注意:#{}里面不一定写id,当占位符只有一个的时候,#{}里面随便写,建议见名知意。
@Test public void testDelete() {SqlSession sqlSession = SqlSessionUtil.openSession();// 传入id删除记录int count = sqlSession.delete("deleteCarById", 33);System.out.println("删除"+count+"条记录");sqlSession.commit();sqlSession.close(); }
==> Preparing: delete from t_car where id = ?
==> Parameters: 33(Integer)
<== Updates: 1
删除1条记录3. 更新
需求:将id=34中的carNum修改为6666,brand修改为五菱宏光,guide_price修改为50.0,produce_time修改为2000-08-22,car_type修改为新能源
<update id="updateCar">update t_car setcar_num = #{carNum},brand = #{brand},guide_price = #{guidePrice},produce_time = #{produceTime},car_type = #{carType}whereid = #{id} </update>
@Test public void testUpdate() {SqlSession sqlSession = SqlSessionUtil.openSession();// 封装数据Car car = new Car();car.setId(34L); // 在增加时id没有给是因为id会自增,对于更新是根据id,所以id必须要传car.setCarNum("6666");car.setBrand("五菱宏光");car.setProduceTime("2000-08-22");car.setGuidePrice(50.0);car.setCarType("新能源");// 传入car执行update语句int count = sqlSession.delete("updateCar", car);System.out.println("更新了几条记录-->"+count);sqlSession.commit();sqlSession.close(); }
==> Preparing: update t_car set car_num = ?, brand = ?, guide_price = ?, produce_time = ?, car_type = ? where id = ?
==> Parameters: 6666(String), 五菱宏光(String), 50.0(Double), 2000-08-22(String), 新能源(String), 34(Long)
<== Updates: 1
更新了几条记录-->14. 查询
4.1 单条查询
<select id="selectCarById">select * from t_car where id = #{id} </select>
@Test public void testSelectById() {SqlSession sqlSession = SqlSessionUtil.openSession();Object car = sqlSession.selectOne("selectCarById", 1);// 查询一条,返回值也只是一个单单的ObjectSystem.out.println(car);sqlSession.close(); }
A query was run and no Result Maps were found for the Mapped Statement 'jiuxao.selectCarById'.
【翻译】:对于⼀个查询语句来说,没有找到查询的结果映射。
It's likely that neither a Result Type nor a Result Map was specified.
【翻译】:很可能既没有指定结果类型,也没有指定结果映射。主要意思:对于一个查询来说,需要指定"结果类型"或者"结果映射"。也就是说,如果查询的结果是一个Java对象的话,必须指定这个Java对象的类型。
之前JDBC处理结果集:在程序中手动创建对象然后将值传入到Java对象的属性中。
ResultSet rs = ps.executeQuery(); if(rs.next){String id = rs.getString("id");String username = rs.getString("username");String password = rs.getString("password");User user = new User();// 给对象属性赋值user.setId(id);user.setUsername(username);user.setPassword(password); }
在Mybatis中,对于<select>标签都有一个resultType属性,这个就是用来指定要转换的类型,内部帮我们实例化对象,并将结果集传入对应的属性上。(该属性只有<select>有,<insert><u><d>没有)
<select id="selectCarById" resultType="com.jiuxiao.pojo.Car"> <!--需要写全限定名-->select * from t_car where id = #{id} </select>
==> Preparing: select * from t_car where id = ?
==> Parameters: 1(Integer)
<== Columns: id, car_num, brand, guide_price, produce_time, car_type
<== Row: 1, 1001, 宝马, 11.00, 2022-02-22, 燃油车
<== Total: 1
Car{id=1, carNum='null', brand='宝马', guidePrice=null, produceTime='null', carType='null'}可以看到控制台不在报错,证明对于查询语句resultType不能省略,但是在日志,可以看到这台记录的每个字段都查询到了值,但是Car对象中只有id和brand有值,为什么?
只需要将字段和属性保持一致即可,在Java中修改属性肯定是不可以的,修改数据库的字段更不可以。在数据库中有一个as别名,将数据库中的字段变为类中的属性。利用这个就可以将表中的字段和类中的属性保持一致,从而将返回的结果集全部封装到Java对象中。
<select id="selectCarById" resultType="com.jiuxiao.pojo.Car">selectid as id,car_num as carNum,brand as brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefrom t_carwhere id = #{id} </select>
==> Preparing: select id as id, car_num as carNum, brand as brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where id = ?
==> Parameters: 1(Integer)
<== Columns: id, carNum, brand, guidePrice, produceTime, carType
<== Row: 1, 1001, 宝马, 11.00, 2022-02-22, 燃油车
<== Total: 1
Car{id=1, carNum='1001', brand='宝马', guidePrice=11.0, produceTime='2022-02-22', carType='燃油车'}���通过测试得知:如果表中字段和类中属性对应不上的话,使用as别名的方式
4.2 查询全部
因为是返回所有,肯定使用List集合来接收,对于resultType里面写什么类型:泛型的类型,因为等会调用的方法返回的就是List,所以只需要指定里面的泛型即可。
<select id="selectCarOfList" resultType="com.jiuxiao.pojo.Car">selectid as id,car_num as carNum,brand as brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefrom t_car </select>
@Test public void testSelectAll() {SqlSession sqlSession = SqlSessionUtil.openSession();List<Car> carList = sqlSession.selectList("selectCarOfList"); // 调用selectList() 返回的是List集合carList.forEach(car -> System.out.println(car)); // stream流 lambda写法 不懂先这样写的sqlSession.close(); }==> Preparing: select id as id, car_num as carNum, brand as brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car ==> Parameters: <== Columns: id, carNum, brand, guidePrice, produceTime, carType <== Row: 1, 1001, 宝马, 11.00, 2022-02-22, 燃油车 <== Row: 2, 1002, 奔驰, 55.00, 2021-05-24, 新能源 <== Row: 22, 1003, 丰田, 11.10, 2021-05-11, 燃油车 <== Row: 32, 1005, 法拉利, 100.00, 2000-05-23, 电动车 <== Row: 34, 6666, 五菱宏光, 50.00, 2000-08-22, 新能源 <== Row: 35, 1006, 丰田, 15.00, 2022-02-22, 燃油车 <== Row: 36, 1006, 丰田, 15.00, 2022-02-22, 燃油车 <== Total: 7 Car{id=1, carNum='1001', brand='宝马', guidePrice=11.0, produceTime='2022-02-22', carType='燃油车'} Car{id=2, carNum='1002', brand='奔驰', guidePrice=55.0, produceTime='2021-05-24', carType='新能源'} Car{id=22, carNum='1003', brand='丰田', guidePrice=11.1, produceTime='2021-05-11', carType='燃油车'} Car{id=32, carNum='1005', brand='法拉利', guidePrice=100.0, produceTime='2000-05-23', carType='电动车'} Car{id=34, carNum='6666', brand='五菱宏光', guidePrice=50.0, produceTime='2000-08-22', carType='新能源'} Car{id=35, carNum='1006', brand='丰田', guidePrice=15.0, produceTime='2022-02-22', carType='燃油车'} Car{id=36, carNum='1006', brand='丰田', guidePrice=15.0, produceTime='2022-02-22', carType='燃油车'}
查询所有东西没有太多,主要是返回的类型要注意一下,是泛型的类型。
5. Mapper.xml中的namespace
namespace翻译过来就是"命名空间"的意思,目的就是为了防止Mapper.xml中每个SQLid有重名的情况
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="abc"><select id="selectCarOfList" resultType="com.jiuxiao.pojo.Car">selectid as id,car_num as carNum,brand as brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefrom t_car</select> </mapper>
在mybatis-conf.xml中注册CarMapper2.xml
<mappers><mapper resource="CarMapper.xml"/><mapper resource="CarMapper2.xml"/> </mappers>
可以看到现在有两个SQLid都为selectCarOfList
@Test public void testSelectAll() {SqlSession sqlSession = SqlSessionUtil.openSession();List<Car> carList = sqlSession.selectList("selectCarOfList"); // 调用selectList() 返回的是List集合carList.forEach(car -> System.out.println(car)); // stream流 lambda写法 不懂先这样写的sqlSession.close(); }
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.IllegalArgumentException: selectCarOfList is ambiguous in Mapped Statements collection (try using the full name including the namespace, or rename one of the entries)
【翻译】selectCarOfList在Mapped Statements集合中不明确(请尝试使用命名空间的全名,或重命名其中的一个条目)主要意思:selectOfList重名了,要么使用命名空间,要么修改其中的一个SQLid
【第一个CarMapper.xml的namespace为"jiuxiao",第二个为"abc"],这里使用第二个
@Test public void testSelectAll() {SqlSession sqlSession = SqlSessionUtil.openSession();List<Car> carList = sqlSession.selectList("abc.selectCarOfList"); // 调用selectList() 返回的是List集合carList.forEach(car -> System.out.println(car)); // stream流 lambda写法 不懂先这样写的sqlSession.close(); }
==> Preparing: select id as id, car_num as carNum, brand as brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car
==> Parameters:
<== Columns: id, carNum, brand, guidePrice, produceTime, carType
<== Row: 1, 1001, 宝马, 11.00, 2022-02-22, 燃油车
<== Row: 2, 1002, 奔驰, 55.00, 2021-05-24, 新能源
<== Row: 22, 1003, 丰田, 11.10, 2021-05-11, 燃油车
<== Row: 32, 1005, 法拉利, 100.00, 2000-05-23, 电动车
<== Row: 34, 6666, 五菱宏光, 50.00, 2000-08-22, 新能源
<== Row: 35, 1006, 丰田, 15.00, 2022-02-22, 燃油车
<== Row: 36, 1006, 丰田, 15.00, 2022-02-22, 燃油车
<== Total: 7
Car{id=1, carNum='1001', brand='宝马', guidePrice=11.0, produceTime='2022-02-22', carType='燃油车'}
Car{id=2, carNum='1002', brand='奔驰', guidePrice=55.0, produceTime='2021-05-24', carType='新能源'}
Car{id=22, carNum='1003', brand='丰田', guidePrice=11.1, produceTime='2021-05-11', carType='燃油车'}
Car{id=32, carNum='1005', brand='法拉利', guidePrice=100.0, produceTime='2000-05-23', carType='电动车'}
Car{id=34, carNum='6666', brand='五菱宏光', guidePrice=50.0, produceTime='2000-08-22', carType='新能源'}
Car{id=35, carNum='1006', brand='丰田', guidePrice=15.0, produceTime='2022-02-22', carType='燃油车'}
Car{id=36, carNum='1006', brand='丰田', guidePrice=15.0, produceTime='2022-02-22', carType='燃油车'}
六、Mybatis核心配置文件详解:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><settings><setting name="logImpl" value="STDOUT_LOGGING"/></settings><environments default="development"><environment id="development"><transactionManager type="JDBC"/><!--数据源--><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/jiuxiao"/><property name="username" value="root"/><property name="password" value="1234"/></dataSource></environment></environments><mappers><mapper resource="CarMapper.xml"/></mappers> </configuration>
1. environments
表示多环境,以(s)结尾,也就就是说里面可以配置多个环境(environment)
一个environment代表一个sqlSessionFactory对象。
default属性表示默认使用哪个数据源,指向的是environment中的id值,必须给值。如果在创建sqlSessionFactory的时候指定要使用的数据源,不再使用default指向的数据源
配置两个环境,第一个环境数据库使用"jiuxiao",第二个环境使用"abc",default属性指向第一个环境的id
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><settings><setting name="logImpl" value="STDOUT_LOGGING"/></settings><!--default指向第一个--><environments default="development"><environment id="development"><transactionManager type="JDBC"/><!--数据源--><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/jiuxiao"/><property name="username" value="root"/><property name="password" value="1234"/></dataSource></environment><!--数据源--><environment id="abc"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/abc"/><property name="username" value="root"/><property name="password" value="1234"/></dataSource></environment></environments><mappers><mapper resource="CarMapper.xml"/></mappers> </configuration>
environment的id为development,数据库为jiuxiao
<select id="selectCarById" resultType="com.jiuxiao.pojo.Car">selectid as id,car_num as carNum,brand as brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefrom t_carwhere id = #{id} </select>
@Test public void testEnvironment() throws IOException {// 不需要工具类SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();// 如果不指定使用的环境,那么使用default中指向的环境,也就是id=development的环境,当中的数据库为jiuxiaoSqlSessionFactory sqlSessionFactory1 = builder.build(Resources.getResourceAsReader("mybatis-conf.xml"));SqlSession sqlSession = sqlSessionFactory1.openSession();// 执行sql语句Car car1 = sqlSession.selectOne("selectCarById", 1);System.out.println(car1);sqlSession.close();// 在build()中有一个重载方法可以指定环境 build("inputstream流","环境的id")SqlSessionFactory sqlSessionFactory2 = builder.build(Resources.getResourceAsReader("mybatis-conf.xml"),"abc"); // 指向id=abc的环境,当中的数据库为abcSqlSession sqlSession1 = sqlSessionFactory2.openSession();Car car2 = sqlSession1.selectOne("selectCarById", 1);System.out.println(car2);sqlSession.close(); }
【environment的id为development,数据库为jiuxiao】
==> Preparing: select id as id, car_num as carNum, brand as brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where id = ?
==> Parameters: 1(Integer)
<== Columns: id, carNum, brand, guidePrice, produceTime, carType
<== Row: 1, 1001, 宝马, 11.00, 2022-02-22, 燃油车
<== Total: 1
【environment的id为abc,数据库为abc】
==> Preparing: select id as id, car_num as carNum, brand as brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where id = ?
==> Parameters: 1(Integer)
<== Columns: id, carNum, brand, guidePrice, produceTime, carType
<== Row: 1, 1222, 奔驰, 20.20, 2000-05-12, 新能源
<== Total: 1
Car{id=1, carNum='1222', brand='奔驰', guidePrice=20.2, produceTime='2000-05-12', carType='新能源'}2. transactionManager
- JDBC:使用JDBC原生的事务管理器,也就是在执行SQL语句之前开始事物conn.setAutoCommit(false),执行后需要提交事物conn.commit()
- MANAGED:将不会接管事物,由其他容器管理,比如Spring。如果没有容器管理,那就代表没有开启事务,也就代表没有事物。没有事物的含义:只要执行一条DML(增删改),就提交一次
3. dataSource
主要用来指定数据源,换句话说就是指定需要使用的数据库连接池。
- UNPOOLED:不使用数据库连接池,但不代表不获取Collection对象。只不过每次获取的都是一个新的连接对象
- 其中的property中可以是:
- key:driver,value:JDBC驱动
- key:url,value:JDBC连接数据库的URL
- key:username,value:登录数据库的用户名
- key:password,value:登录数据库的密码
- key:defaultTransactionIsolationLevel,value:默认的连接事务隔离级别
- key:defaultNetworkTimeout,value:等待数据库操作完成的默认⽹络超时时间
- key:driver.encoding,value:UTF8
- POOLED:使用数据库连接池,每次使用的Collection对象都是从连接池中获取。mybatis中已经有对应的实现
- 其中的property中可以是:
- 除了包含UNPOOLED之外
- key:poolMaximumActiveConnections,value:连接池中最大活跃的连接数(也就是最大可以使用的连接数)
- key:poolMaximumIdleConnections,value:任意时间可能存在的连接数(如果设置为5,这时连接池中有6个空闲连接,就删除一个)
- 其他:参考Mybatis中文网
不使用数据库连接池会是什么样的?每次都需要建立连接,消耗资源,消耗时间
<environments default="development"><environment id="development"><transactionManager type="JDBC"/><!--数据源--><dataSource type="UNPOOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/jiuxiao"/><property name="username" value="root"/><property name="password" value="1234"/></dataSource></environment> </environments>
@Test public void testDataSource() throws IOException {SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();// 如果不指定使用的环境,那么使用default中指向的环境,也就是id=development的环境,当中的数据库为jiuxiaoSqlSessionFactory sqlSessionFactory = builder.build(Resources.getResourceAsReader("mybatis-conf.xml"));// 执行sql语句for (int i = 0; i < 2; i++) {SqlSession sqlSession = sqlSessionFactory.openSession();Car car = sqlSession.selectOne("selectCarById", 1);System.out.println(car);sqlSession.close();} }
运行程序:可以看到两个collection对象不是同一个,就证明第二次是新建了一个连接
<environments default="development"><environment id="development"><transactionManager type="JDBC"/><!--数据源--><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/jiuxiao"/><property name="username" value="root"/><property name="password" value="1234"/></dataSource></environment> </environments>
Java程序不变,运行结果:两个collection对象都是同一个,说明都是从连接池中获取
4. properties
Mybatis中提供了更加灵活的配置,比如将连接数据库的信息放在这个标签中:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><properties><property name="jdbc.driver" value="com.mysql.jdbc.Driver"/><property name="jdbc.url" value="jdbc:mysql://localhost:3306/jiuxiao"/><property name="jdbc.username" value="root"/><property name="jdbc.password" value="1234"/></properties><settings><setting name="logImpl" value="STDOUT_LOGGING"/></settings><environments default="development"><environment id="development"><transactionManager type="JDBC"/><!--数据源--><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/jiuxiao"/><property name="username" value="root"/><property name="password" value="1234"/></dataSource></environment></environments><mappers><mapper resource="CarMapper.xml"/></mappers> </configuration>
<dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/> </dataSource>
在resources目录下新建jdbc.properties文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/jiuxiao
jdbc.username=root
jdbc.password=1234在<properties>标签中使用resource属性进行引入:
<properties resource="jdbc.properties"/>
@Test public void testProperties() {SqlSession sqlSession = SqlSessionUtil.openSession();Car car = sqlSession.selectOne("selectCarById", 1);System.out.println(car);sqlSession.close(); }
==> Preparing: select id as id, car_num as carNum, brand as brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where id = ?
==> Parameters: 1(Integer)
<== Columns: id, carNum, brand, guidePrice, produceTime, carType
<== Row: 1, 1001, 宝马, 11.00, 2022-02-22, 燃油车
<== Total: 1
Car{id=1, carNum='1001', brand='宝马', guidePrice=11.0, produceTime='2022-02-22', carType='燃油车'}
- resource:这个属性从类的根路径下开始查找【常用】
- url:绝对路径,如果这个文件放到了d:/jdbc.properties,那么url写成:file:///d:/jdbc.properties。file:///+文件路径,之前演示过,不演示了。
5. mappers
底层原理:
1.
在进行增加或者更新的时候,传入pojo对象,在Mapper.xml文件中的#{}里写入属性就能向数据库增加记录,这是为什么?传入别的还可以吗?
首先我们要知道Mybatis底层执行的是JDBC代码,所有我们在Mapper.xml文件中写的#{},执行的时候变为?占位符,也就是原始JDBC的格式。
其次它底层是先获取到#{}中的属性,然后在前面拼接get,并将该属性的首字母大写,最后将剩余的部分拼接到后面。说到这里应该也能想到这就是在将一个属性一点点的拼接成该属性的get方法。所以Mybatis在执行SQL语句的时候,底层相当于在调用get方法来获取值。然后将值添加到数据库中
例如:name-->Name-->getName-->method.invoke()
可以,在#{}中我写入abc,但是在这个类中并没有,为什么还能。
例如:将#{name}修改成#{abc},那么只需要将getName()修改为getAbc()即可
2.
在进行查询的时候,如何将返回的结果集,添加到pojo对象中。
在Mybatis执行完SQL语句后,获取到返回的字段。然后和上面一样,只不是前面是拼接set,也就是调用某个属性的set方法,然后将值作为参数传入。
例如返回name字段:先使用原生JDBC的代码,通过字段获取到值,然后在进行添加值。
ps.getString("name")-->name-->Name-->setName()-->setName(值)
ps.setString("user_name")-->User_name-->setUser_name(值)
3.
在Mapper.xml里面都是一个个的SQL语句与一些标签。
那么这些东西也对应着一个JAVA对象,叫做:MappedStatement。
这个对象里面主要封装了:SQL语句,标签中的属性,比如resultType等。
如何精准的找到这个对象:使用sqlId,底层里面使用了一个类似于Map集合的东西。key存储的是sqlId,value存储的就是MappenStatement对象。
所以我们开发中,在使用sqlId去调用Mapper.xml中的方法时,底层实际上在操作这个MappedStatement对象
七、Javassist
Javassist是一个可以在程序运行阶段动态的在内存中生成类
1. 导入jar
<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.29.1-GA</version> </dependency>
2. 使用
2.1 使用javassist创建一个类与调用方法
@Test public void testGenerate() throws Exception {// 获取类池ClassPool pool = ClassPool.getDefault();// 制造类CtClass ctClass = pool.makeClass("com.jiuxiao.dao.impl.AccountImpl");// 制造方法String method = "public void insert(){System.out.println(123);}";CtMethod ctMethod = CtMethod.make(method, ctClass);// 添加到类中ctClass.addMethod(ctMethod);// 生成字节码Class<?> clazz = ctClass.toClass();// 创建对象Object obj = clazz.newInstance();// 获取方法Method insertMethod = clazz.getDeclaredMethod("insert");// 调用insertMethod.invoke(obj); }
2.2 让一个类去实现接口与实现方法
package com.jiuxiao.dao;public interface AccountDao {void delete(); }java程序:@Test public void testGenerateInterface() throws Exception {// 获取类池ClassPool pool = ClassPool.getDefault();// 创建类CtClass ctClass = pool.makeClass("com.jiuxiao.dao.impl.AccountDaoImpl");// 实现接口(需要实现的接口)CtClass ctInterface = pool.makeInterface("com.jiuxiao.dao.AccountDao");// 添加接口ctClass.addInterface(ctInterface);// 实现方法(实现方法也就是将接口中的方法写一遍 只不过不是抽象方法)String method = "public void delete(){System.out.println(\"Hello world!\");}";CtMethod make = CtMethod.make(method, ctClass);// 添加方法ctClass.addMethod(make);// 获取字节码文件Class<?> clazz = ctClass.toClass();AccountDao account = (AccountDao) clazz.newInstance();account.delete(); }
3. 在应用中生成接口的实现类
在编写程序中,每个层和每个层之间需要使用接口来进行传递数据(解耦合),对于Dao层编写有必要去写实现类吗?
没必要,因为代码都是一行或者几行,没有太多的逻辑。而且还有重复的地方:
public class AccountDaoImpl implements AccountDao {@Overridepublic Account selectByActno(String actNo) {SqlSession sqlSession = SqlSessionUtil.openSqlSession();Account account = (Account) sqlSession.selectOne("account.selectByActno", actNo);return account;}@Overridepublic int update(Account account) {SqlSession sqlSession = SqlSessionUtil.openSqlSession();int count = sqlSession.update("account.update",account);return count;} }
那么我们可以使用javassist将这些Dao层的实现类,动态的在内存中创建,在程序中不再编写。
3.1 准备工具类和生成实现类的方法
package com.jiuxiao.utils;/** * 生成实现类(代理类) * @author 酒萧 * @since 1.0 * @version 1.0 */ public class GenerateProxy {private GenerateProxy(){}/*** 生成目标接口的实现类* @param inter 接口 既然需要生成实现类,接口不能少* @return 实现类(代理类)*/public static Object getMapper(Class inter){} }
3.2 先将类和接口创建出来。
public static Object getMapper(Class inter){// 获取类池ClassPool pool = ClassPool.getDefault();// 创建实现类 通过传入的接口基础上进行拼接CtClass ctClass = pool.makeClass(inter.getName() + ".impl" + inter.getSimpleName() + "Proxy"); // com.jiuxiao.dao.CarDao.impl.CarDaoProxy// 生成接口 通过传入的接口来动态的获取CtClass ctInterface = pool.makeInterface(inter.getName()); // com.jiuxiao.dao.CarDao// 实现接口ctClass.addInterface(ctInterface);// 实现方法try {// 创建方法CtMethod ctMethod = CtMethod.make("方法", ctClass);// 将方法添加到类中ctClass.addMethod(ctMethod);} catch (Exception e) {throw new RuntimeException(e);}try {// 获取到类对象Object obj = ctClass.toClass().newInstance();// 返回return obj;} catch (Exception e) {throw new RuntimeException(e);} }
这部分也是最麻烦的,因为我们不知道接口中有多少的方法,每一个方法的方法名是什么,返回值等。之前都是写死的,现在不能写死,需要动态的获取
3.3 拼接形式参数
// 实现方法 // 1.拿到接口中所有的方法 Method[] methods = inter.getDeclaredMethods(); // 2.开始遍历 Arrays.stream(methods).forEach(method -> { StringBuilder sb = new StringBuilder(); // 必须放在里面 每次都是一个全新的// void delete(); --> public void delete(){}// 3.拼接public(因为接口中的方法都是公共的 public直接写死即可)sb.append("public");sb.append(" "); // 空格// 4.拼接返回值Class<?> returnType = method.getReturnType();sb.append(returnType.getName());sb.append(" ");// 5.拼接方法名String name = method.getName();sb.append(name);// 6.拼接参数sb.append("(");Class<?>[] parameterTypes = method.getParameterTypes();for (int i = 0; i < parameterTypes.length; i++) {Class<?> parameterType = parameterTypes[i];sb.append(parameterType.getName());sb.append("arg"+i); // 因为参数有多个,不能重复 arg0....// 拼接","if (i != parameterTypes.length - 1){ // (String agr0,String arg1) 0 != 1 , 1 != 1sb.append(",");}}sb.append(")");// 7.拼接方法体 });
拼接到这里整个方法的签名语句获取到了,那么方法体也就是一个大麻烦:因为我们不知道别人会在方法中进行什么操作:查询、增加、更新、删除。这些都不知道。
3.4 拼接方法体
先将一些一致的代码写死:因为不需要动态的获取,比如获取SqlSession
sb.append(")"); // 7.拼接方法体 sb.append("{"); // 注意写包名 让javassist找到这是哪个包下的 sb.append("org.apache.ibatis.session.SqlSession sqlSession = com.jiuxiao.utils.SqlSessionUtil.openSqlSession();"); sb.append("}");
下面就是一些crud语句,所以肯定少不了SqlSession对象,需要外界传入过来,形参接收。
public static Object getMapper(SqlSession sqlSession, Class inter){}
只要获取该语句在Mapper.xml中的标签名,就知道别人在干什么、
SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement("sqlId").getSqlCommandType();
我们无法获取到,因为sqlId是别人随便写的。所以:既然获取不到,那我们就限制范围
现在我们可以获取到接口的全限名 和 方法名,那么我们就可以将这两个拼接成字符串作为sqlId。
我们之前传入sqlId是通过namespace+标签id拼接,那么现在就要修改成:
package com.jiuxiao.dao;import com.jiuxiao.pojo.Car;public interface CarDao {Car selectById(Long id);int update(Car car); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jiuxiao.dao.CarDao"> <!--接口全限名--><!--id为接口方法名--><select id="selectById" resultType="com.jiuxiao.pojo.Car">select * from t_car where id = #{id}</select><update id="update">update t_car set car_num=#{carNum},brand=#{brand} where id = #{id}</update> </mapper>
注意:在调用select insert时sqlId由双引号引用起来的
// 7.拼接方法体 sb.append("{"); // 注意写包名 让javassist找到这是哪个包下的 sb.append("org.apache.ibatis.session.SqlSession.SqlSession sqlSession = com.jiuxiao.utils.SqlSessionUtil.SqlSessionUtil.openSqlSession();"); // 获取sqlId String sqlId = inter.getName() +"."+ name; // com.jiuxiao.dao.CarDao.selectById(); // 获取到Mapper.xml中的标签名 select insert ... SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement("sqlId").getSqlCommandType(); // 判断 if (sqlCommandType == SqlCommandType.SELECT){// 查询语句最终返回的时候,有可能别人会进行强转,所以需要处理一下:提前强转成方法的返回值类型sb.append("return ("+returnType.getName()+")sqlSession.selectOne(\""+sqlId+"\",arg0);"); // 这里的参数为上面拼接的参数 } if (sqlCommandType == SqlCommandType.INSERT){// 增加sb.append("return sqlSession.insert(\""+sqlId+"\",arg0);"); } if (sqlCommandType == SqlCommandType.UPDATE){// 更新sb.append("return sqlSession.update(\""+sqlId+"\",arg0);"); } if (sqlCommandType == SqlCommandType.DELETE){// 删除sb.append("return sqlSession.delete(\""+sqlId+"\",arg0);"); } sb.append("}");
sb.append("}"); try {// 创建方法CtMethod ctMethod = CtMethod.make(sb.toString(), ctClass);// 将方法添加到类中ctClass.addMethod(ctMethod); } catch (Exception e) {throw new RuntimeException(e); }
3.5 最终整个方法的效果
/** * 生成目标接口的实现类 * @param inter 接口 * @param sqlSession sql会话 * @return 实现类(代理类) */ public static Object getMapper(SqlSession sqlSession, Class inter){// 获取类池ClassPool pool = ClassPool.getDefault();// 创建实现类 通过传入的接口基础上进行拼接CtClass ctClass = pool.makeClass(inter.getName() + ".impl" + inter.getSimpleName() + "Proxy"); // com.jiuxiao.dao.CarDao.impl.CarDaoProxy// 生成接口 通过传入的接口来动态的获取CtClass ctInterface = pool.makeInterface(inter.getName()); // com.jiuxiao.dao.CarDao// 实现接口ctClass.addInterface(ctInterface);// 1.拿到接口中所有的方法Method[] methods = inter.getDeclaredMethods();// 2.开始遍历Arrays.stream(methods).forEach(method -> {// 实现方法StringBuilder sb = new StringBuilder();// void delete(); --> public void delete(){}// 3.拼接public(因为接口中的方法都是公共的 public直接写死即可)sb.append("public");sb.append(" "); // 空格// 4.拼接返回值Class<?> returnType = method.getReturnType();sb.append(returnType.getName());sb.append(" ");// 5.拼接方法名String name = method.getName();sb.append(name);// 6.拼接参数sb.append("(");Class<?>[] parameterTypes = method.getParameterTypes();for (int i = 0; i < parameterTypes.length; i++) {Class<?> parameterType = parameterTypes[i];sb.append(parameterType.getName());sb.append(" ");sb.append("arg"+i); // 因为参数有多个,不能重复 arg0....// 拼接","if (i != parameterTypes.length - 1){ // (String agr0,String arg1) 0 != 1 , 1 != 1sb.append(",");}}sb.append(")");// 7.拼接方法体sb.append("{");// 注意写包名 让javassist找到这是哪个包下的sb.append("org.apache.ibatis.session.SqlSession sqlSession = com.jiuxiao.utils.SqlSessionUtil.openSqlSession();");String sqlId = inter.getName() +"."+ name;SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();// 判断if (sqlCommandType == SqlCommandType.SELECT){// 查询sb.append("return ("+returnType.getName()+")sqlSession.selectOne(\""+sqlId+"\",arg0);");}if (sqlCommandType == SqlCommandType.INSERT){// 增加sb.append("return sqlSession.insert(\""+sqlId+"\",arg0);");}if (sqlCommandType == SqlCommandType.UPDATE){// 更新sb.append("return sqlSession.update(\""+sqlId+"\",arg0);");}if (sqlCommandType == SqlCommandType.DELETE){// 删除sb.append("return sqlSession.delete(\""+sqlId+"\",arg0);");}sb.append("}");try {// 创建方法CtMethod ctMethod = CtMethod.make(sb.toString(), ctClass);// 将方法添加到类中ctClass.addMethod(ctMethod);} catch (Exception e) {throw new RuntimeException(e);}}); try { // 获取到类对象 Object obj = ctClass.toClass().newInstance(); // 返回 return obj; } catch (Exception e) { throw new RuntimeException(e); } }
3.6 测试程序
@Test public void testProxy() {CarDao mapper = (CarDao) GenerateProxy.getMapper(SqlSessionUtil.openSqlSession(), CarDao.class);mapper.selectById(1L); }
==> Preparing: select * from t_car where id = ?
==> Parameters: 1(Long)
<== Columns: id, car_num, brand, guide_price, produce_time, car_type
<== Row: 1, 1001, 宝马, 11.00, 2022-02-22, 燃油车
<== Total: 1将id是1的记录修改:car_num=5678,brand=丰田
@Test public void testProxy() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarDao mapper = (CarDao) GenerateProxy.getMapper(sqlSession, CarDao.class);Car car = new Car();car.setId(1L);car.setCarNum("5678");car.setBrand("丰田");mapper.update(car);sqlSession.commit(); }
==> Preparing: update t_car set car_num=?,brand=? where id = ?
==> Parameters: 5678(String), 丰田(String), 1(Long)
<== Updates: 1Mybatis中提供了一个API,专门用来获取这种实现类对象。也可以说是代理对象
@Test public void testMapper() {// 提供了getMapper(),底层实现就是Javassist。别人已经封装好了CarDao mapper = SqlSessionUtil.openSqlSession().getMapper(CarDao.class);mapper.selectById(1L); }
==> Preparing: select * from t_car where id = ?
==> Parameters: 1(Long)
<== Columns: id, car_num, brand, guide_price, produce_time, car_type
<== Row: 1, 5678, 丰田, 11.00, 2022-02-22, 燃油车
<== Total: 14. 小结
- 在我们使用getMapper调用接口方法时,底层使用Javassist代理机制,为我们在内存中生成一个代理实现类,帮我们去调用insert update delete select这些方法,转而执行Mapper.xml文件中所定义的SQL语句。
- 调用一个SQL语句需要通过SQLid去指向,这个sqlId通过namespace+标签的id值组成。也就是通过接口的全限名+方法名拼接成的字符串。这个字符串将会作为sqlId。
- 所以以后在Mapper.xml文件中的namespace的值是接口的全限定名,标签中的id值是接口中的方法名。这样在执行接口方法时,Mybatis底层为我们创建的实现类才能找个找个sql语句。
八、#{}和${}的区别
1. 根据汽车类型来查询汽车信息
package com.jiuxiao.pojo;/** * Car * @author 酒萧 * @version 1.0 * @since 1.0 */ public class Car {private Long id;private String carNum;private String brand;private Double guidePrice;private String produceTime;private String carType;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getCarNum() {return carNum;}public void setCarNum(String carNum) {this.carNum = carNum;}public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public Double getGuidePrice() {return guidePrice;}public String getProduceTime() {return produceTime;}public void setProduceTime(String produceTime) {this.produceTime = produceTime;}public void setGuidePrice(Double guidePrice) {this.guidePrice = guidePrice;}public String getCarType() {return carType;}public void setCarType(String carType) {this.carType = carType;}@Overridepublic String toString() {return "Car{" +"id=" + id +", carNum='" + carNum + '\'' +", brand='" + brand + '\'' +", guidePrice=" + guidePrice +", produceTime='" + produceTime + '\'' +", carType='" + carType + '\'' +'}';} }
/** * Car Mapper接口 */ public interface CarMapper {/*** 根据汽车类型查询汽车信息* @param carType 汽车类型* @return 汽车信息*/List<Car> selectByCarType(String carType);}
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jiuxiao.mapper.CarMapper"><select id="selectByCarType" resultType="com.jiuxiao.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherecar_type = #{carType}</select> </mapper>
@Test public void testSelectByCarType() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List<Car> cars = mapper.selectByCarType("新能源");cars.forEach(car-> System.out.println(car));sqlSession.close(); }
==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = ?
==> Parameters: 新能源(String)
<== Columns: id, carNum, brand, guidePrice, produceTime, carType
<== Row: 2, 1002, 奔驰, 55.00, 2021-05-24, 新能源
<== Row: 34, 6666, 五菱宏光, 50.00, 2000-08-22, 新能源
<== Row: 39, 1212, 好了, 50.00, 2000-08-22, 新能源
<== Row: 40, 1212, 好了, 50.00, 2000-08-22, 新能源
<== Total: 4selectid, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carTypefromt_carwherecar_type = ?
<select id="selectByCarType" resultType="com.jiuxiao.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherecar_type = ${carType} </select>
==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = 新能源
==> Parameters:selectid, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carTypefromt_carwherecar_type = 新能源
- #{}:底层使用的是PreparedStatement对象,可以防止SQL注入,先编译SQL语句,然后再执行
- ${}:底层使用的是Statement对象,有SQL注入的风险,直接将值拼接在SQL语句中
- 当#{}无法处理时,使用${}。也就是需要先将参数拼接在SQL语句中,在执行。那就使用${},或者SQL语句中需要由外界传入SQL关键字,那么使用${},因为使用#{}的话,会在关键字外面拼接上'',这样的话,那么就不是关键字了,只是一个普通的字符串。比如排序的关键字:asc或者desc,或者表名。
- 如果只是普通的传值,那么优先使用#{}
2. 批量删除
/** * 根据多个id删除多条记录 * @param ids id字符串 * @return 影响条数 */ int deleteAllById(String ids);
<delete id="deleteAllById">delete from t_car where id in (#{ids}) </delete>
@Test public void testDeleteAllById() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);int i = mapper.deleteAllById("1,2");System.out.println(i);sqlSession.commit();sqlSession.close(); }
可以看到直接是将1,2当成一个整体放入在()中,所以最终的SQL语句为:
delete from t_car where id in ('1,2')
<delete id="deleteAllById">delete from t_car where id in (${ids}) </delete>
3. 模糊查询
/** * 根据汽车品牌模糊查询 * @param brand 汽车品牌 * @return 汽车信息 */ List<Car> selectByBrandOfLike(String brand);
<select id="selectByBrandOfLike" resultType="com.jiuxiao.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherebrand like '%#{brand}%' </select>
@Test public void testSelectByBrandOfLike() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List<Car> cars = mapper.selectByBrandOfLike("奔驰");cars.forEach(car-> System.out.println(car)); }
?占位符直接被'%%'包起来,这样的话,底层JDBC在赋值的时候找不到这个?,因为JDBC找的占位符是:like ?,但是这个SQL语句却是:like '%?%',只是将?当做这个字符串的一部分。
<select id="selectByBrandOfLike" resultType="com.jiuxiao.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherebrand like '%${brand}%' </select>
<select id="selectByBrandOfLike" resultType="com.jiuxiao.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherebrand like concat('%',#{brand},'%') </select>
注意:%外面使用的是双引号,使用单引号查询不出来任何数据。必须使用双引号
<select id="selectByBrandOfLike" resultType="com.jiuxiao.pojo.Car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwherebrand like "%"#{brand}"%" </select>
九、核心配置文件的其他属性
1. 别名机制:
在Mapper.xml文件中,有的SQL语句需要返回POJO的类型,那么就需要在标签的resultType属性中写入这个POJO的全限定名。
Mybatis为我们提供了一种别名机制,也就是配置了这个属性,以后不需要再写全限定名,只需要写入别名即可。
这个在typeAliases标签中,为:typeAliase。
<settings><setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!--起别名--> <typeAliases><typeAlias type="" alias=""></typeAlias> </typeAliases>
- type:POJO的全限定名
- alias:别名
<typeAliases><!--为Car起一个别名:abc--><typeAlias type="com.jiuxiao.pojo.Car" alias="abc"/> </typeAliases>
如果有多个POJO需要起别名,就写多个typeAlias即可
将Mapper.xml文件中的resultType值修改成别名:
@Test public void testSelectByCarType() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List<Car> cars = mapper.selectByCarType("新能源");cars.forEach(car-> System.out.println(car));sqlSession.close(); }
==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = ?
==> Parameters: 新能源(String)
<== Columns: id, carNum, brand, guidePrice, produceTime, carType
<== Row: 34, 6666, 五菱宏光, 50.00, 2000-08-22, 新能源
<== Total: 1
Car{id=34, carNum='6666', brand='五菱宏光', guidePrice=50.0, produceTime='2000-08-22', carType='新能源'}@Test public void testSelectByBrandOfLike() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List<Car> cars = mapper.selectByBrandOfLike("奔驰");cars.forEach(car-> System.out.println(car)); }
==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where brand like "%"?"%"
==> Parameters: 奔驰(String)
<== Columns: id, carNum, brand, guidePrice, produceTime, carType
<== Row: 41, 1006, 奔驰2, 10.00, 2001-01-20, 油燃车
<== Row: 42, 1005, 奔驰1, 5.00, 2005-05-15, 油燃车
<== Total: 2
Car{id=41, carNum='1006', brand='奔驰2', guidePrice=10.0, produceTime='2001-01-20', carType='油燃车'}
Car{id=42, carNum='1005', brand='奔驰1', guidePrice=5.0, produceTime='2005-05-15', carType='油燃车'}可以看到不管是小写还是大写,都可以运行成功,那么就证明这个别名不区分大小写。但是不能写错
这个typeAlias中的alias属性可以省略,那么省略后,这个别名是什么:
<typeAliases><typeAlias type="com.jiuxiao.pojo.Car"/> </typeAliases>
/** * 根据汽车id查询信息 * @param id id * @return 汽车信息 */ Car selectById(Integer id);
<select id="selectById" resultType="cAr"> <!--别名-->selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwhereid = #{id} </select>
@Test public void testSelectById() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = mapper.selectById(32);System.out.println(car);sqlSession.close(); }
==> Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where id = ?
==> Parameters: 32(Integer)
<== Columns: id, carNum, brand, guidePrice, produceTime, carType
<== Row: 32, 1005, 法拉利, 100.00, 2000-05-23, 电动车
<== Total: 1
Car{id=32, carNum='1005', brand='法拉利', guidePrice=100.0, produceTime='2000-05-23', carType='电动车'}如果我们有多个POJO类,那么就需要配置多个typeAlias标签,所以Mybatis为我们提供了包的机制:
<typeAliases><!--<typeAlias type="com.jiuxiao.pojo.Car"/>--><!--指定包的路径即可--><package name="com.jiuxiao.pojo"/> </typeAliases>
2. 注册Mapper
注册mapper的方式,也就是在mybatis-conf.xml中的mappers中填写Mapper.xml的方式:
- resource:从类的根路径下开始查找(之前说过)
- url:从任意位置开始查找(之前说过)
还是一种注册的方式是class,指定一个接口,mybatis会从该接口包下查找同名的Mapper.xml文件。
例如:指定的接口为com.jiuxiao.mapper.CarMapper,那么mybatis就会从com/jiuxiao/mapper/包下查找CarMapper.xml文件。
<mappers><!--指定一个接口--><mapper class="com.jiuxiao.mapper.CarMapper"/> </mappers>
查看目录:先不修改CarMapper.xml,查看会不会有问题
可以异常信息的主要原因就是:没有找到com.jiuxiao.mapper.CarMapper.seelectById这个SQL语句。至于为什么这么长,是因为接口的全限名+方法名拼接而成
在resources目录下创建com.jiuxiao.mapper.CarMapper目录
注意:在resources下没有包的概念,只有目录,所有在建的时候要么一个一个的建,要么使用这种方式:com/jiuxiao/mapper,如果使用com.jiuxiao.mapper,那么会视为文件名,并不会分层
再次查看目录:上面java是类的根路径,下面resources是类的根路径。只是可视化工具的问题。
���提醒:如果使用class的这种方式,接口和xml文件必须在同一个目录下,名字必须一致。
接口有多个,需要配置多个class,所以mybatis提供了包的机制:
<mappers><!--指定一个接口--><!--<mapper class="com.jiuxiao.mapper.CarMapper"/>--><!--指定mapper接口所在的包--><package name="com.jiuxiao.mapper"/> </mappers>
3. 返回主键
在一些业务中,一张表需要使用另一张表的主键,这时另外一张的表的主键不是人工干扰。比如使用数据库的自增。那么我们获取起来就比较麻烦,需要增加完再进行查询,不能增加完就返回主键。
/** * 增加完汽车信息后返回主键 * @param car * @return */ int insertReturnId(Car car);
<insert id="insertReturnId">insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType}) </insert>
- useGeneratedKeys:是否使用自动生成的主键
- keyProperty:返回的值添加到对象的哪个属性上。
<insert id="insertReturnId" useGeneratedKeys="true" keyProperty="id">insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType}) </insert>
@Test public void testInsertReturnId() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car();car.setCarNum("1007");car.setBrand("宝马");car.setGuidePrice(50.0);car.setProduceTime("2022-10-01");car.setCarType("新能源");mapper.insertReturnId(car);// 获取该对象的主键System.out.println(car.getId());sqlSession.commit();sqlSession.close(); }
==> Preparing: insert into t_car (id,car_num,brand,guide_price,produce_time,car_type) values(null,?,?,?,?,?)
==> Parameters: 1007(String), 宝马(String), 50.0(Double), 2022-10-01(String), 新能源(String)
<== Updates: 1
43
十、Mybatis参数处理
package com.jiuxiao.pojo;import java.util.Date;public class Student {private Long id;private String name;private String address;private Date birth;private Character sex;private Double height;@Overridepublic String toString() {return "Student{" +"id=" + id +", name='" + name + '\'' +", address='" + address + '\'' +", birth=" + birth +", sex=" + sex +", height=" + height +'}';}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public Date getBirth() {return birth;}public void setBirth(Date birth) {this.birth = birth;}public Character getSex() {return sex;}public void setSex(Character sex) {this.sex = sex;}public Double getHeight() {return height;}public void setHeight(Double height) {this.height = height;} }
1. 传入简单类型
- int,byte,short,long,float,double,char
- Integer,Byte,Short,Long,Float,Double,Cahracter
- String
- Date
- 根据id查询学生
- 根据name查询学生
- 根据birth查询学生
- 根据性别查询学生
/** * 根据id查询学生信息 * @param id 学生id * @return 学生信息 */ Student selectById(Long id);
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jiuxiao.mapper.StudentMapper"><select id="selectById" resultType="student">select * from t_student where id = #{id}</select> </mapper>
@Test public void testSelectById() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.selectById(1L);System.out.println(student);sqlSession.close(); }
Student{id=1, name='张三', address='北京', birth=Sat Oct 01 00:00:00 CST 2022, sex=男, height=1.5}
其实想说的是:在SQL映射文件的标签中有一个parameterType属性。
- 为了什么:
- 之前我们在进行传入参数的时候没有写过这个属性,为什么也可以。这是因为mybatis底层有类型的自动推算,它可以拿到我们接口中的参数类型,然后再一个个的判断。
- 如果我们写入的这个参数,指定类型,那么mybatis底层就会根据你传入的类型直接进行ps.setLong(),因为mybatis的底层是通过JDBC代码执行,JDBC代码赋值是通过ps.setXxxx(),所以如果我们指定了类型,可以直接让mybatis迅速的知道传入什么类型。提高了程序的执行效率
- 它还有内置的一套别名:查看mybatis中文网
���这个属性想写就写,不想写就不写,绝大部分都是不需要写的,只不过写了可以提高一些效率
<select id="selectById" resultType="student"> <!--指定类型-->select * from t_student where id = #{id,javaType=Long,jdbcType=BIGINT} </select>
- javaType:该字段在java中的类型
- jdbcType:该字段在数据库中的类型
它和parameterType一样,可以省略。其中如果参数只有一个的话,#{}里面是可以随便写的
<select id="selectById" resultType="student">select * from t_student where id = #{gagagaga} <!--随便给--> </select>
/** * 通过name查询学生 * @param name 学生name * @return 相关的学生信息 */ List<Student> selectByName(String name);/** * 根据birth查询学生 * @param birth * @return */ List<Student> selectByBirth(Date birth);/** * 通过sex查询学生 * @param sex * @return */ List<Student> selectBySex(Character sex);
<select id="selectByName" resultType="student" parameterType="string">select * from t_student where name = #{name} </select><select id="selectByBirth" resultType="student" parameterType="java.util.Date">select * from t_student where birth = #{birth} </select><select id="selectBySex" resultType="student">select * from t_student where sex = #{sex} </select>
@Test public void testSelectByName() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List<Student> students = mapper.selectByName("张三");students.forEach(student -> System.out.println(students));sqlSession.close(); }
[Student{id=1, name='张三', address='北京', birth=Sat Oct 01 00:00:00 CST 2022, sex=男, height=1.5}]
@Test public void testSelectByBirth() throws Exception {SqlSession sqlSession = SqlSessionUtil.openSqlSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");Date birth = sdf.parse("2022-09-01");List<Student> students = mapper.selectByBirth(birth);students.forEach(student -> System.out.println(students));sqlSession.close(); }
[Student{id=2, name='李四', address='河南', birth=Thu Sep 01 00:00:00 CST 2022, sex=女, height=1.75}]
@Test public void testSelectBySex() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List<Student> students = mapper.selectBySex(Character.valueOf('男'));students.forEach(student -> System.out.println(students));sqlSession.close(); }
[Student{id=1, name='张三', address='北京', birth=Sat Oct 01 00:00:00 CST 2022, sex=男, height=1.5}]
2. 传入Map
/** * 根据map中的参数 对数据库增加记录 * @param map * @return */ int insertStudentByMap(Map<String,Object> map);
<insert id="insertStudentByMap" parameterType="map">insert into t_student(id,name,address,birth,sex,height) values(null,#{姓名},#{地址},#{生日},#{性别},#{身高}) </insert>
@Test public void testInsertStudentByMap() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Map<String,Object> map = new HashMap<>();map.put("姓名","王五");map.put("地址","山东");map.put("生日",new Date());map.put("性别",'男');map.put("身高",1.77);mapper.insertStudentByMap(map);sqlSession.commit();sqlSession.close(); }
3. 传入POJO
之前说过,#{}里面必须写POJO中的属性,因为mybatis底层会去调用属性的set/get方法。
/** * 根据pojo对象 对数据库增加记录 * @param student * @return */ int insertStudentByPOJO(Student student);
<insert id="insertStudentByPOJO">insert into t_student(id,name,address,birth,sex,height) values(null,#{name},#{address},#{birth},#{sex},#{height}) </insert>
@Test public void testInsertStudentByPOJO() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = new Student();student.setName("赵六");student.setAddress("河北");student.setBirth(new Date());student.setSex('女');student.setHeight(1.65);mapper.insertStudentByPOJO(student);sqlSession.commit();sqlSession.close(); }
4. 传入多个参数
/** * 根据address和sex查询学生信息 * @param address * @param sex * @return */ List<Student> selectByAddressAndSex(String address,Character sex);
<select id="selectByAddressAndSex" resultType="student">select * from t_student where address = #{address} and sex = #{sex} </select>
@Test public void testSelectByAddressAndSex() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);List<Student> students = mapper.selectByAddressAndSex("河南", '女');students.forEach(student -> System.out.println(students));sqlSession.close(); }
主要信息:'address'参数找不到,可以尝试使用[arg0,agr1,param1,param2]
<select id="selectByAddressAndSex" resultType="student">select * from t_student where address = #{arg0} and sex = #{arg1} </select>
<select id="selectByAddressAndSex" resultType="student">select * from t_student where address = #{param1} and sex = #{param2} </select>
<select id="selectByAddressAndSex" resultType="student">select * from t_student where address = #{arg0} and sex = #{param2} </select>
- arg0是第一个参数
- arg1是第二个参数
- param1是第一个参数
- param2是第二个参数
底层原理:mybatis在底层会创建一个Map集合,将arg0/param1作为key,方法上的参数作为value
Map<string,Object> map = new HashMap<>(); map.put("arg0",name); map.put("arg1",sex); map.put("param1",name); map.put("param2",sex);
5. @param注解
上面的那种可读性太差,所以mybatis为我们提供了这种注解,可以替代arg0和arg1
List<Student> selectByAddressAndSex(@Param("address") String address,@Param("sex") Character sex);
<select id="selectByAddressAndSex" resultType="student">select * from t_student where address = #{arg0} and sex = #{arg0} </select>
主要:找不到'arg0',可以使用[address,sex,param1,param2]
可以看到param1和param2还可以继续使用,不演示了。
将StudentMapper.xml文件修改成addres和sex:
<select id="selectByAddressAndSex" resultType="student">select * from t_student where address = #{address} and sex = #{sex} </select>
为什么使用了注解后,arg0和arg1不能使用。使用的是注解中的参数呢?
6. @param源码
十一、Mybatis结果集处理
package com.jiuxiao.pojo;/** * Car * @author 酒萧 * @version 1.0 * @since 1.0 */ public class Car {private Long id;private String carNum;private String brand;private Double guidePrice;private String produceTime;private String carType;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getCarNum() {return carNum;}public void setCarNum(String carNum) {this.carNum = carNum;}public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public Double getGuidePrice() {return guidePrice;}public String getProduceTime() {return produceTime;}public void setProduceTime(String produceTime) {this.produceTime = produceTime;}public void setGuidePrice(Double guidePrice) {this.guidePrice = guidePrice;}public String getCarType() {return carType;}public void setCarType(String carType) {this.carType = carType;}@Overridepublic String toString() {return "Car{" +"id=" + id +", carNum='" + carNum + '\'' +", brand='" + brand + '\'' +", guidePrice=" + guidePrice +", produceTime='" + produceTime + '\'' +", carType='" + carType + '\'' +'}';} }
1. 返回一条Car记录
package com.jiuxiao.mapper;import com.jiuxiao.pojo.Car;public interface CarMapper {/*** 根据id查询汽车信息,返回一条记录* @return*/Car selectById(Long id); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jiuxiao.mapper.CarMapper"><select id="selectById" resultType="car">select*from t_carwhere id = #{id}</select> </mapper>
@Test public void testSelectById() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = mapper.selectById(32L);System.out.println(car);sqlSession.close(); }
Car{id=32, carNum='null', brand='法拉利', guidePrice=null, produceTime='null', carType='null'}
可以看到除了id和brand有值,其他都为空,这是因为数据库表中的字段和POJO对象中的属性名没有映射,所以之前在查询的时候使用as别名的方式来映射
<select id="selectById" resultType="car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_carwhereid = #{id} </select>
Car{id=32, carNum='1005', brand='法拉利', guidePrice=100.0, produceTime='2000-05-23', carType='电动车'}
全部有值,证明都数据库表中的字段和POJO对象中的属性名一一映射上了。
2. 返回多条Car记录
/** * 查询所有的汽车信息,返回多条记录 * @return */ List<Car> selectAll();
<select id="selectAll" resultType="car">selectid,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carTypefromt_car </select>
@Test public void testSelectAll() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List<Car> cars = mapper.selectAll();cars.forEach(car-> System.out.println(car));sqlSession.close(); }
Car{id=32, carNum='1005', brand='法拉利', guidePrice=100.0, produceTime='2000-05-23', carType='电动车'}
Car{id=34, carNum='6666', brand='五菱宏光', guidePrice=50.0, produceTime='2000-08-22', carType='新能源'}
Car{id=41, carNum='1006', brand='奔驰2', guidePrice=10.0, produceTime='2001-01-20', carType='油燃车'}
Car{id=42, carNum='1005', brand='奔驰1', guidePrice=5.0, produceTime='2005-05-15', carType='油燃车'}
Car{id=43, carNum='1007', brand='宝马', guidePrice=50.0, produceTime='2022-10-01', carType='新能源'}/** * 测试返回多条记录,使用一个Car对象接收 * @return */ Car selectAll();
SQL语句不变,测试程序返回值换成Car对象。运行程序出现异常:
TooManyResultsException:应该返回一条记录或者没有记录,但是却返回了5条记录。
可以看到底层调用的是selectOne(),所以这种方式不行。
3. 返回一条Map集合
场景:当从数据库中查询的字段没有合适的POJO对象接收时,可以使用Map集合进行接收。
/** * 根据car_num查询 返回一条记录 使用map集合接收 * @param carNum * @return */ Map<String,Object> selectByCarNumReturnMap(String carNum);
<select id="selectByCarNumReturnMap" resultType="map"><!--返回类型为map 别名-->select*fromt_carwherecar_num = #{carNum} </select>
@Test public void testSelectByCarNumReturnMap() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Map<String, Object> map = mapper.selectByCarNumReturnMap("6666");System.out.println(map);sqlSession.close(); }
执行结果:将字段作为map的key,值作为map的value。
{car_num=6666, id=34, guide_price=50.00, produce_time=2000-08-22, brand=五菱宏光, car_type=新能源}
4. 返回多条Map集合
/** * 根据brand模糊查询,返回多条map集合 * @param brand * @return */ List<Map<String,Object>> selectByBrandLikeReturnMap(String brand);
SQL语句:因为返回值是List,所以底层会调用selectList(),因此只需要关心集合中的每条元素类型即可
<select id="selectByBrandLikeReturnMap" resultType="map"><!--返回类型是泛型的类型-->select*fromt_carwherebrand like "%"#{brand}"%" </select>
@Test public void testSelectByBrandLikeReturnMap() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);List<Map<String, Object>> maps = mapper.selectByBrandLikeReturnMap("奔驰");maps.forEach(map-> System.out.println(map));sqlSession.close(); }
{car_num=1006, id=41, guide_price=10.00, produce_time=2001-01-20, brand=奔驰2, car_type=油燃车}
{car_num=1005, id=42, guide_price=5.00, produce_time=2005-05-15, brand=奔驰1, car_type=油燃车}5. 返回大Map集合
场景:通过某个值,查询某条记录。如果使用List,那么就需要先遍历,然后再一个个的判断。使用大Map的方式,将这个值作为map的key,记录作为Map集合。这样能够快速的查找
/** * 查询所有 返回一个大Map * 将数据库表中的id作为map的key * 将数据库表中的每条记录作为map的value * @return */ @MapKey("id") // 数据库中哪个字段作为map的key Map<Long,Map<String,Object>> selectAllReturnMap();
<select id="selectAllReturnMap" resultType="map"><!--最终返回的是一条条的记录-->select*fromt_car </select>
@Test public void testSelectAllReturnMap() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Map<Long, Map<String, Object>> maps = mapper.selectAllReturnMap();System.out.println(maps);sqlSession.close(); }
{
32={car_num=1005, id=32, guide_price=100.00, produce_time=2000-05-23, brand=法拉利, car_type=电动车},
34={car_num=6666, id=34, guide_price=50.00, produce_time=2000-08-22, brand=五菱宏光, car_type=新能源},
41={car_num=1006, id=41, guide_price=10.00, produce_time=2001-01-20, brand=奔驰2, car_type=油燃车},
42={car_num=1005, id=42, guide_price=5.00, produce_time=2005-05-15, brand=奔驰1, car_type=油燃车},
43={car_num=1007, id=43, guide_price=50.00, produce_time=2022-10-01, brand=宝马, car_type=新能源}
}Map<Long, Map<String, Object>> maps = mapper.selectAllReturnMap();
Map<String, Object> map = maps.get(32L);
System.out.println(map);{car_num=1005, id=32, guide_price=100.00, produce_time=2000-05-23, brand=法拉利, car_type=电动车}
6. resultMap手动映射
如果一直起别名就会特别麻烦,所以mybatis提供了resultMap这个属性,将返回的结果集一一映射。
<mapper namespace="com.jiuxiao.mapper.CarMapper"><!--id:select语句在使用的resultMap属性时指定的唯一标识type:返回的结果集需要和哪一个POJO映射--><resultMap id="Bean_Result" type="car"><!--这里有别名--><!--配置id这个标签可以提高mybatis的效率 也可以不配置--><id property="id" column="id"></id><!--property:POJO中的属性名column:数据库表中的字段名--><result property="carNum" column="car_num"></result><!--如果一致,可以不配置--><!--<result property="brand" column="brand"></result>--><result property="guidePrice" column="guide_price"></result><result property="produceTime" column="produce_time"></result><result property="carType" column="car_type"></result></resultMap>
<select id="selectAll" resultMap="Bean_Result"><!--指向resultMap的id值-->select*fromt_car </select>
Car{id=32, carNum='1005', brand='法拉利', guidePrice=100.0, produceTime='2000-05-23', carType='电动车'}
Car{id=34, carNum='6666', brand='五菱宏光', guidePrice=50.0, produceTime='2000-08-22', carType='新能源'}
Car{id=41, carNum='1006', brand='奔驰2', guidePrice=10.0, produceTime='2001-01-20', carType='油燃车'}
Car{id=42, carNum='1005', brand='奔驰1', guidePrice=5.0, produceTime='2005-05-15', carType='油燃车'}
Car{id=43, carNum='1007', brand='宝马', guidePrice=50.0, produceTime='2022-10-01', carType='新能源'}7. 使用开启驼峰命名自动映射:
- 属性名需要遵循Java中的命名规范,数据中的字段名需要遵循SQL的命名规范
- java:首字母小写,后面的每个首字母大写
- SQL:全部小写,每个单词使用下划线分割
java属性名
carNum
car_num
在mybatis-conf.xml核心配置文件的settings中配置:
<settings><setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
<select id="selectById" resultType="car">select*fromt_carwhereid = #{id} </select>
Car{id=32, carNum='1005', brand='法拉利', guidePrice=100.0, produceTime='2000-05-23', carType='电动车'}
8. 返回总条数
/** * 获取总条数 * @return */ Long selectCount();
<select id="selectCount" resultType="long">select count(1) from t_car </select>
@Test public void testSelectCount() {Long total = SqlSessionUtil.openSqlSession().getMapper(CarMapper.class).selectCount();System.out.println("总条数:"+total); }
==> Preparing: select count(1) from t_car
==> Parameters:
<== Columns: count(1)
<== Row: 5
<== Total: 1
总条数:5
十二、动态SQL
可以根据不同的条件查询不同的结果,让SQL语句变得更加灵活。
package com.jiuxiao.pojo;/*** Car* @author 酒萧* @version 1.0* @since 1.0*/ public class Car {private Long id;private String carNum;private String brand;private Double guidePrice;private String produceTime;private String carType;public Car() {}public Car(Long id, String carNum, String brand, Double guidePrice, String produceTime, String carType) {this.id = id;this.carNum = carNum;this.brand = brand;this.guidePrice = guidePrice;this.produceTime = produceTime;this.carType = carType;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getCarNum() {return carNum;}public void setCarNum(String carNum) {this.carNum = carNum;}public String getBrand() {return brand;}public void setBrand(String brand) {this.brand = brand;}public Double getGuidePrice() {return guidePrice;}public String getProduceTime() {return produceTime;}public void setProduceTime(String produceTime) {this.produceTime = produceTime;}public void setGuidePrice(Double guidePrice) {this.guidePrice = guidePrice;}public String getCarType() {return carType;}public void setCarType(String carType) {this.carType = carType;}@Overridepublic String toString() {return "Car{" +"id=" + id +", carNum='" + carNum + '\'' +", brand='" + brand + '\'' +", guidePrice=" + guidePrice +", produceTime='" + produceTime + '\'' +", carType='" + carType + '\'' +'}';} }
1. if标签
该标签中只有一个test属性,这个属性的值要么是true或者false。如果是true那么标签中的SQL语句将拼接在前面的SQL语句后面
public interface CarMapper {/*** 多条件查询* @param brand 品牌* @param carType 类型* @param guidePrice 价格* @return*/List<Car> selectByBrandOrTypeOrGuidePrice(@Param("brand") String brand, @Param("carType") String carType, @Param("guidePrice") Double guidePrice); }
- test中传入的参数是方法中@param注解中的的参数名
- 如果没有使用@param注解,那么就是arg0 或者 param1
- 如果参数是一个POJO对象,里面传入的是该对象中的属性
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.jiuxiao.mapper.CarMapper"><select id="selectByBrandOrTypeOrGuidePrice" resultType="car">select *from t_carwhere<!--这里的brand是参数名 如果是POJO,那么需要传入属性名--><if test="brand != null and brand != ''">brand like "%"#{brand}"%"</if><if test="carType != null and carType != ''">and car_type = #{carType}</if><if test="guidePrice != null and guidePrice != ''">and guide_price > #{guidePrice}</if></select></mapper>
@Test public void testSelectByBrandOrTypeOrGuidePrice() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 先将条件全部传入List<Car> cars = mapper.selectByBrandOrTypeOrGuidePrice("奔驰","油燃车",5.0);cars.forEach(car-> System.out.println(car));sqlSession.close(); }
@Test public void testSelectByBrandOrTypeOrGuidePrice() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 只传入第一个参数List<Car> cars = mapper.selectByBrandOrTypeOrGuidePrice("奔驰",null,null);cars.forEach(car-> System.out.println(car));sqlSession.close(); }
@Test public void testSelectByBrandOrTypeOrGuidePrice() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 都不传List<Car> cars = mapper.selectByBrandOrTypeOrGuidePrice(null,null,null);cars.forEach(car-> System.out.println(car));sqlSession.close(); }
异常的原因:where后面没有跟条件,为了保证就算都不传入参数也能继续查询,在where后面给一个永久成立的语句,修改之后的SQL语句:
<select id="selectByBrandOrTypeOrGuidePrice" resultType="car">select *from t_carwhere 1=1 <!--1=1永久成立--><!--这里的brand是参数名 如果是POJO,那么需要传入属性名--><if test="brand != null and brand != ''"><!--前面加and,因为多个条件使用and分割-->and brand like "%"#{brand}"%"</if><if test="carType != null and carType != ''">and car_type = #{carType}</if><if test="guidePrice != null and guidePrice != ''">and guide_price > #{guidePrice}</if> </select>
2. where标签
作用:让where子句更加灵活,能够根据判断随时添加where或删除where关键字。
当where标签中条件都不成立,那么where标签保证不会生成where子句
可以自动删除某些条件前面的and或者or,注意:是删除前面,后面的无法删除。
<select id="selectByBrandOrTypeOrGuidePrice" resultType="car">select *from t_car<where> <!--如果里面的判断都不成立,那么where子句不会生成--><if test="brand != null and brand != ''"><!--前面加一下and,看看会不会自动删除-->and brand like "%"#{brand}"%"</if><if test="carType != null and carType != ''">and car_type = #{carType}</if><if test="guidePrice != null and guidePrice != ''">and guide_price > #{guidePrice}</if></where> </select>
@Test public void testSelectByBrandOrTypeOrGuidePrice() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 先将条件全部传入List<Car> cars = mapper.selectByBrandOrTypeOrGuidePrice("奔驰","油燃车",5.0);cars.forEach(car-> System.out.println(car));sqlSession.close(); }
执行结果,可以看到自动的在条件前面拼接一个WHERE,并将最前面的and删除了:
@Test public void testSelectByBrandOrTypeOrGuidePrice() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 先将条件全部传入List<Car> cars = mapper.selectByBrandOrTypeOrGuidePrice(null,null,null);cars.forEach(car-> System.out.println(car));sqlSession.close(); }
3. trim标签
- prefix:在trim标签中的语句前添加内容
- suffix:在trim标签中的语句后添加内容
- prefixOverrides:剔除前缀
- suffixOverrides:剔除后缀
需求:增加一个汽车信息,通过trim和if标签动态的添加需要提交的参数,没有提交的为null
/** * 通过trim标签和if标签来动态的进行增加 * @param car * @return */ int insertByTrim(Car car);
<insert id="insertByTrim">insert into t_car<trim prefix="(" suffix=")"> <!--在前面和后面添加( )-->id,<if test="carNum != null and carNum != ''">car_num,</if><if test="brand != null and brand != ''">brand,</if><if test="guidePrice != null and guidePrice != ''">guide_price,</if><if test="produceTime != null and produceTime != ''">produce_time,</if><if test="carType != null and carType != ''">car_type</if></trim>values<trim prefix="(" suffix=")"> <!--在前面和后面添加( )-->null,<if test="carNum != null and carNum != ''">#{carNum},</if><if test="brand != null and brand != ''">#{brand},</if><if test="guidePrice != null and guidePrice != ''">#{guidePrice},</if><if test="produceTime != null and produceTime != ''">#{produceTime},</if><if test="carType != null and carType != ''">#{carType}</if></trim> </insert>
@Test public void testInsertByTrim() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car();car.setCarNum("1008");car.setBrand("比亚迪");car.setGuidePrice(20.0);car.setProduceTime("1990-05-25");car.setCarType("电动车");int count = mapper.insertByTrim(car);System.out.println(count);sqlSession.commit();sqlSession.close(); }
4. set标签
使用在update语句中,用来生产set关键字。同时去掉多余","
比如我们只提交不为空的字段,值为空或者为空串,不修改原来的值。
/** * 根据set标签动态的更新提交的数据 * @param car * @return */ int updateBySet(Car car);
<update id="updateBySet">updatet_carsetcar_num = #{carNum},brand = #{brand},guide_price = #{guidePrice},produce_time = #{produceTime},car_type = #{carType}whereid = #{id} </update>
@Test public void testUpdateBySet() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(42L, "1234", "兰博基尼", 50.1, "1988-12-10", "电动车");int i = mapper.updateBySet(car);System.out.println(i);sqlSession.commit();sqlSession.close(); }
@Test public void testUpdateBySet() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(42L, "6666", "特斯拉",null,null,null);int i = mapper.updateBySet(car);System.out.println(i);sqlSession.commit();sqlSession.close(); }
<update id="updateBySet">updatet_car<set><if test="carNum != null and carNum != ''">car_num = #{carNum},</if><if test="brand != null and brand != ''">brand = #{brand},</if><if test="guidePrice != null and guidePrice != ''">guide_price = #{guidePrice},</if><if test="produceTime != null and produceTime != ''">produce_time = #{produceTime},</if><if test="carType">car_type = #{carType},</if></set>where id = #{id} </update>
@Test public void testUpdateBySet() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car(44L, "5678", "丰田", null,null,null);int i = mapper.updateBySet(car);System.out.println(i);sqlSession.commit();sqlSession.close(); }
5. forEach标签
- collection:指定需要遍历的数据或者集合
- item:每次遍历后的元素,也就是数据或集合中的每个元素
- separator:每个循环语句之间的分割符
- open:以什么开始
- close:以什么结尾
5.1 批量删除
/*** 使用in的方式批量删除* @param ids* @return*/ int deleteAllByIn(Long[] ids);
<delete id="deleteAllByIn">delete from t_car where id in<!--delete from t_car where id in (id,id,id)--><foreach collection="ids" separator="," item="id" open="(" close=")">#{id} <!--数据中的每个元素--></foreach> </delete>
@Test public void testDeleteAllByIn() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Long[] ids = {42L,43L,44L};int count = mapper.deleteAllByIn(ids);System.out.println(count);sqlSession.commit();sqlSession.close(); }
绑定异常:没有找到'id',但是可以使用[array,arg0]
修改SQL语句中的参数,将collection修中的ids修改成array:
<delete id="deleteAllByIn">delete from t_car where id in<!--delete from t_car where id in (id,id,id)--><foreach collection="array" separator="," item="id" open="(" close=")">#{id} <!--数据中的每个元素--></foreach> </delete>
Map<String,Object> map = new HashMap<>(); map.put("array",ids); map.put("arg0",ids);
如果使用注解,那么参数就不能使用array。需要使用注解中的参数名:
/** * 使用in的方式批量删除 * @param ids * @return */ int deleteAllByIn(@Param("ids") Long[] ids);
没有找到'array',但是可以使用[ids,param1]
<delete id="deleteAllByIn">delete from t_car where<!--delete from t_car where id = 34 or id = 41--><foreach collection="ids" item="id" separator="or">id = #{id}</foreach> </delete>
5.2 批量增加
int insertAll(@Param("cars") List<Car> cars);
<insert id="insertAll">insert into t_car<!--insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,111),(null,111)--><trim prefix="(" suffix=")">id,car_num, brand, guide_price,produce_time,car_type</trim>values<foreach collection="cars" separator="," item="car"><trim prefix="(" suffix=")"><!--集合里面是一个个的car对象-->null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType}</trim></foreach> </insert>
@Test public void testInsertAll() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car1 = new Car(null,"1006","宝马",10.2,"2005-05-15","新能源");Car car2 = new Car(null,"1007","奔驰",50.5,"1889-12-05","油燃车");Car car3 = new Car(null,"1008","比亚迪",5.2,"2020-02-22","电动车");List<Car> carList = Arrays.asList(car1, car2, car3);int count = mapper.insertAll(carList);System.out.println(count);sqlSession.commit();sqlSession.close(); }
6. SQL标签与include
<sql id="Bean_list"><!--id用来include去指向--><!--定义代码片段-->id,car_num, brand, guide_price,produce_time,car_type </sql><select id="selectByBrandOrTypeOrGuidePrice" resultType="car">select <include refid="Bean_list"/> <!--指向SQL标签中的id-->from t_car<where><if test="brand != null and brand != ''"><!--前面加一下and,看看会不会自动删除-->and brand like "%"#{brand}"%"</if><if test="carType != null and carType != ''">and car_type = #{carType}</if><if test="guidePrice != null and guidePrice != ''">and guide_price > #{guidePrice}</if></where> </select>
十三、高级映射与延迟加载
1. 多对一
package com.jiuxiao.pojo;/** * 班级类 * 一方 */ public class Clazz {private Integer cid;private String cname;@Overridepublic String toString() {return "Clazz{" +"cid=" + cid +", cname='" + cname + '\'' +'}';}public Integer getCid() {return cid;}public void setCid(Integer cid) {this.cid = cid;}public String getCname() {return cname;}public void setCname(String cname) {this.cname = cname;} }
package com.jiuxiao.pojo;/** * 学生表 * 多方 */ public class Student {private Integer sid;private String sname;// 持有一方的对象private Clazz clazz;@Overridepublic String toString() {return "Student{" +"sid=" + sid +", sname='" + sname + '\'' +", clazz=" + clazz +'}';}public Integer getSid() {return sid;}public void setSid(Integer sid) {this.sid = sid;}public String getSname() {return sname;}public void setSname(String sname) {this.sname = sname;}public Clazz getClazz() {return clazz;}public void setClazz(Clazz clazz) {this.clazz = clazz;} }
- 一条SQL语句,借助resultMap使用级联属性映射
- 一条SQL语句,借助resultMap中的associaction标签
- 两条SQL语句,借助resultMap分步查询(常用:可复用,支持懒加载)
1.1 级联属性映射
public interface StudentMapper {/*** 使用级联属性方式 查询学生和所在班级* @param sid 学生id* @return 学生信息*/Student selectById(Integer sid);}
<resultMap id="bean_property" type="student"> <!--这里有别名--><id property="sid" column="sid"></id><result property="sname" column="sname"></result><!--clazz是一个对象,指向他下面的属性--><result property="clazz.cid" column="cid"></result><result property="clazz.cname" column="cname"></result> </resultMap><select id="selectById" resultMap="bean_property">selects.sid,s.sname,c.cnamefrom<!--多对一 那么多的一方就是主表-->t_stu s left join t_clazz c on s.cid = c.cidwheres.sid = #{sid} </select>
@Test public void testSelectById() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.selectById(1);System.out.println("学生名称--> "+student.getSname());System.out.println("所在班级--> "+student.getClazz().getCname());sqlSession.close(); }
1.2 使用association
/** * 使用association完成连表查询 * @param sid * @return */ Student selectByIdOfAssociation(Integer sid);
<resultMap id="bean_association" type="student"><id property="sid" column="sid"></id><result property="sname" column="sname"></result><!--property:另一张表在该对象中的属性名 这里为clazzjavaType:在java中是什么类型,指向一个类的全限定名--><association property="clazz" javaType="clazz"> <!--这里有别名--><id property="cid" column="cid"></id><result property="cname" column="cname"></result></association> </resultMap><select id="selectByIdOfAssociation" resultMap="bean_association">selects.sid,s.sname,c.cnamefrom<!--多对一 那么多的一方就是主表-->t_stu s left join t_clazz c on s.cid = c.cidwheres.sid = #{sid} </select>
@Test public void testSelectByIdOfAssociation() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.selectByIdOfAssociation(2);System.out.println("学生名称--> "+student.getSname());System.out.println("所在班级--> "+student.getClazz().getCname());sqlSession.close(); }
���1.3 分步查询
第一处:不需要指向一个java类,去指向一个sqlId。其中column属性作为传入该sqlId的参数
<resultMap id="bean_association" type="student"><id property="sid" column="sid"></id><result property="sname" column="sname"></result><!--property:另一张表在该对象中的属性名 这里为clazzjavaType:在java中是什么类型,指向一个类的全限定名--><association property="clazz"select="com.jiuxiao.mapper.ClazzMapper.selectById"column="cid"/><!--指向一个sqlId:接口全限名+方法名--><!--colum:需要的参数--> </resultMap> <!--这里只需要查询学生表--> <select id="selectByIdOfAssociation" resultMap="bean_association"><!--将查询后的cid传入第二步的方法参数中-->select sid,sname,cid from t_stu where sid = #{sid} </select>
public interface ClazzMapper {Clazz selectById(Integer cid); }
<mapper namespace="com.jiuxiao.mapper.ClazzMapper"><select id="selectById" resultType="clazz">select cname from t_clazz where cid = #{cid}</select> </mapper>
测试原来的程序,查看执行结果:可以看到执行了两条SQL语句。
- 重复调用:如果这时有一个需求是通过学生的id查询学生信息,并不需要对应的班级信息那么就只执行查询学生的这条SQL。相同,如果只需要查询班级也是一样
- 懒加载:
什么使用使用到就什么时候加载
- 不使用就不会加载
<association property="clazz"select="com.jiuxiao.mapper.ClazzMapper.selectById"column="cid"fetchType="lazy"/>
@Test public void testSelectByIdOfAssociation() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.selectByIdOfAssociation(2);System.out.println("学生名称--> "+student.getSname());// 不去获取班级信息// System.out.println("所在班级--> "+student.getClazz().getCname());sqlSession.close(); }
将注释放开再次执行:先去查的学生表,因为设置了懒加载。所以等调用的时候才执行
<settings><setting name="lazyLoadingEnabled" value="true"/> </settings>
这样每个resultMap里面都会有懒加载,如果某个不想使用懒加载,将其中的fetchType设置成eager
<association property="clazz"select="com.jiuxiao.mapper.ClazzMapper.selectById"column="cid"fetchType="eager"/>
2. 一对多
一对多:一方为主表,在这里Clazz为主表,这个POJO如何设计:
在clazz类中使用一个集合存储student。对应多个学生
public class Clazz {private Integer cid;private String cname;private List<Student> studentList;// 构造方法// tostring// setter getter }
- 使用collection标签
- 分步查询
2.1 使用collection
/** * 使用collection进行查询班级信息 已经对应的学生 * @param cid * @return */ Clazz selectByIdOfCollection(Integer cid);
<resultMap id="bean_column" type="clazz"><id property="cid" column="cid"/><result property="cname" column="cname"/><!--property:属性名ofType:类型--><collection property="studentList" ofType="student"><id property="sid" column="sid"/><result property="sname" column="sname"/></collection> </resultMap><select id="selectByIdOfCollection" resultMap="bean_column">selectc.cid,c.cname,s.sid,s.snamefromt_clazz c left join t_stu s on c.cid = s.cidwherec.cid = #{cid} </select>
@Test public void testSelectByIdOfCollection() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);Clazz clazz = mapper.selectByIdOfCollection(1001);System.out.println("班级名称--> "+clazz.getCname());clazz.getStudentList().forEach(student -> System.out.println("学生姓名--> "+student.getSname()));sqlSession.close(); }
班级名称--> 高三一班
学生姓名--> 小明
学生姓名--> 小花2.2 使用分步查询
<resultMap id="bean_column" type="clazz"><id property="cid" column="cid"/><result property="cname" column="cname"/><!--property:属性名ofType:类型--><collection property="studentList"select="com.jiuxiao.mapper.StudentMapper.selectByCid"column="cid"/> </resultMap><select id="selectByIdOfCollection" resultMap="bean_column">select cid,cname from t_clazz where cid = #{cid} </select>
List<Student> selectByCid(Integer cid);
<select id="selectByCid" resultType="student">select sname from t_stu where cid = #{cid} </select>
因为在mybatis-conf.xml配置文件中设置了全局懒加载,所以直接使用:
@Test public void testSelectByIdOfCollection() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);Clazz clazz = mapper.selectByIdOfCollection(1001);System.out.println("班级名称--> "+clazz.getCname());// 不获取学生信息// clazz.getStudentList().forEach(student -> System.out.println("学生姓名--> "+student.getSname()));sqlSession.close(); }
十四、Mybatis缓存
缓存的作用:通过减少IO的方式,在内存中操作,从而提高程序的执行效率
mybatis中的缓存:将查询后的语句放在缓存中,下一次的查询语句如果和上一次的查询语句一致,那么就直接从缓存中,不会去查询库。
- 一级缓存:将查询到的结果放在sqlSession中,作用域是同一个sqlSession。两个不同的sqlSession对象, 对应两个缓存
- 二级缓存:将查询到的结果放在sqlSessionFactory中,作用域是同一个sqlSessionFactory。两个不同的sqlSessionFactory对象,对应两个缓存
- 第三方缓存组件:EhCache【java语言开发】,Memcache【C语言开发】
Mybatis中的缓存只针对DQL有效,也就是只存储select语句
1. 一级缓存
只要查询语句都是同一个,都在一个sqlSession中,那么就会走缓存。
public interface CarMapper {/*** 根据id查询汽车信息* @param id* @return*/Car selectById(Long id); }
<mapper namespace="com.jiuxiao.mapper.CarMapper"><select id="selectById" resultType="car">select * from t_car where id = #{id}</select> </mapper>
@Test public void testSelectById() throws IOException {// 不能用工具类SqlSessionFactory build = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-conf.xml"));SqlSession sqlSession = build.openSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 这条查询语句,会被存储到sqlSession中Car car1 = mapper.selectById(32L);System.out.println(car1);// 第二次相同的查询语句,走缓存Car car2 = mapper.selectById(32L);System.out.println(car2);sqlSession.close(); }
- sqlSession对象不同
- 查询语句不同
@Test public void testSelectById() throws IOException {// 不能用工具类SqlSessionFactory build = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-conf.xml"));// 第一个sqlSession对象SqlSession sqlSession1 = build.openSession();CarMapper mapper = sqlSession1.getMapper(CarMapper.class);// 这条查询语句,会被存储到sqlSession1中Car car1 = mapper.selectById(32L);System.out.println(car1);// 第二个sqlSession对象SqlSession sqlSession2 = build.openSession();CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);// 这条查询语句,会被存储到sqlSession2中Car car2 = mapper2.selectById(32L);System.out.println(car2);sqlSession1.close();sqlSession2.close(); }
sqlSession.clearCache();SqlSession sqlSession = build.openSession(); CarMapper mapper = sqlSession.getMapper(CarMapper.class);// 这条查询语句,会被存储到sqlSession中 Car car1 = mapper.selectById(32L); System.out.println(car1);// 手动清空缓存 sqlSession.clearCache();// 第二次相同的查询语句,走缓存 Car car2 = mapper.selectById(32L); System.out.println(car2);sqlSession.close();
/** * 删除学生信息 */ int deleteStudent();
<delete id="deleteStudent">delete from t_student where id = #{1} </delete>
2. 二级缓存
查询语句都是在同一个sqlSessionFactory对象,那么就会走二级缓存
- 在mybatis-conf.xml中设置<setting name="cacheEnable" value="true"/>全局地开启或关闭所有映射文件中已配置的任何缓存。默认是true,无需配置
- 在需要使用二级缓存的sqlMappe.xml文件中添加配置:<cache/>
- 在二级缓存中的实体类必须实现序列化接口,也就是实现java.io.Serializable接口
- sqlSession对象进行提交或关闭后,才会写入到二级缓存中。
public class Car implements Serializable {
// ...
}@Test public void testSelectById() throws IOException {// 同一个sqlSessionFactory对象SqlSessionFactory build = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-conf.xml"));// 第一个sqlSessionSqlSession sqlSession1 = build.openSession();// 这条查询过后会进入到sqlSession1的一级缓存中CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);Car car1 = mapper1.selectById(32L);System.out.println(car1);// 关闭sqlSession后,才会进入到二级缓存中sqlSession1.close();// 第二个sqlSessionSqlSession sqlSession2 = build.openSession();// 这条查询过后会进入到sqlSession2的一级缓存中CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);Car car2 = mapper2.selectById(32L);System.out.println(car2);sqlSession2.close(); }
二级缓存如何失效:只要两次查询之间,执行insert update delete操作,缓存就会失效【无论是哪一张表】
十五、Mybatis逆向工程
<build><plugins><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.4.1</version><configuration><overwrite>true</overwrite></configuration></plugin></plugins> </build>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfigurationPUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//E N""http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration><!--targetRuntime有两个值:MyBatis3Simple:⽣成的是基础版,只有基本的增删改查。MyBatis3:⽣成的是增强版,除了基本的增删改查之外还有复杂的增删改查。--><context id="DB2Tables" targetRuntime="MyBatis3"><!--防⽌⽣成重复代码--><plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/><commentGenerator><!--是否去掉⽣成⽇期--><property name="suppressDate" value="true"/><!--是否去除注释--><property name="suppressAllComments" value="true"/></commentGenerator><!--连接数据库信息--><jdbcConnection driverClass="com.mysql.jdbc.Driver"connectionURL="jdbc:mysql://localhost:3306/xxxx"userId="root"password="1234"></jdbcConnection><!-- ⽣成pojo包名和位置 --><javaModelGenerator targetPackage="com.jiuxiao.pojo" targetProject="src/main/java"><!--是否开启⼦包--><property name="enableSubPackages" value="true"/><!--是否去除字段名的前后空⽩--><property name="trimStrings" value="true"/></javaModelGenerator><!-- ⽣成SQL映射⽂件的包名和位置 --><sqlMapGenerator targetPackage="com.jiuxiao.mapper" targetProject="src/main/resources"><!--是否开启⼦包--><property name="enableSubPackages" value="true"/></sqlMapGenerator><!-- ⽣成Mapper接⼝的包名和位置 --><javaClientGeneratortype="xmlMapper"targetPackage="com.jiuxiao.mapper"targetProject="src/main/java"><property name="enableSubPackages" value="true"/></javaClientGenerator><!-- 表名和对应的实体类名--><table tableName="t_car" domainObjectName="Car"/></context> </generatorConfiguration>
十六、Mybatis注解式开发
1. 增加 @Insert
@Insert("insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})") int insert(Car car);@Test public void testInsert() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car();car.setCarNum("1122");car.setBrand("比亚迪");car.setProduceTime("2022-02-22");car.setGuidePrice(50.0);car.setCarType("电动车");int count = mapper.insert(car);System.out.println(count);sqlSession.commit();sqlSession.close(); }
2. 删除 @Delete
@Delete("delete from t_car where id = #{id}") int deleteById(Integer id);
@Test public void testDeleteById() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);int count = mapper.deleteById(54);System.out.println(count);sqlSession.commit();sqlSession.close(); }
3. 更新 @Update
@Update("update t_car set car_num = #{carNum},brand = #{brand},guide_price = #{guidePrice},produce_time = #{produceTime},car_type = #{carType} where id = #{id}") int update(Car car);
@Test public void testUpdate() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = new Car();car.setId(32L);car.setCarNum("6666");car.setBrand("宝马");car.setProduceTime("1899-05-12");car.setGuidePrice(5.20);car.setCarType("油燃车");int count = mapper.update(car);System.out.println(count);sqlSession.commit();sqlSession.close(); }
4. 查询 @Select
@Select("select * from t_car where id = #{id}") Car selectById(Long id);
@Test public void testSelectById() {SqlSession sqlSession = SqlSessionUtil.openSqlSession();CarMapper mapper = sqlSession.getMapper(CarMapper.class);Car car = mapper.selectById(32L);System.out.println(car);sqlSession.close(); }
5. 映射 @Results
@Select("select * from t_car where id = #{id}") @Results({@Result(property = "id",column = "id"),@Result(property = "carNum",column = "car_num"),@Result(property = "brand",column = "brand"),@Result(property = "guidePrice",column = "guide_price"),@Result(property = "produceTime",column = "produce_time"),@Result(property = "carType",column = "car_type"), }) Car selectById(Long id);