定制网站框架从零入门老杜版笔记(上)
一、MyBatis概述
1.1 framework
1.2 MyBatis和JDBC的关系
MyBatis定制网站是增强版的jdbc,定制网站把一些功能通过反射封装好了
1.3 JDBC不足
- sql语句写死,定制网站如果拓展功能,可能整个sql语句作废
- set操作繁琐,如果sql定制网站语句有一百个?定制网站可能要写一百个set
1.4 了解MyBatis
- MyBatis是对jdbc的封装
- MyBatis之前叫做ibatis,定制网站后来才改名
- MyBatis定制网站是持久层框架
1.5 了解ORM
- O(Object):JVM中的java对象
- R(Relational):定制网站关系型数据库
- M(Mapping):映射
- 什么是ORM?:JavaBean定制网站与数据表记录的互相映射
二、MyBatis入门程序
2.1 定制网站建数据库表
定制网站添加两条字段
2.2 加载mybatis定制网站的五个步骤
- 配置Maven环境,定制网站打包方式设置为jar,加载MyBatis,MySQL
<packaging>jar</packaging><dependencies> <!-- MyBatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.11</version> </dependency> <!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> </dependencies>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 新建,编辑mybatis-config.xml文件(放入resources文件夹)
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/powernode"/> <property name="username" value="xxxx"/> <property name="password" value="xxxx"/> </dataSource> </environment> </environments> <mappers> <mapper resource="CarMapper.xml"/> </mappers></configuration>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 新建,配置xxxMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="org.mybatis.example.BlogMapper"> <insert id=""> insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values (null,1003,"面包车",13.00,"2020-10-13","飞行汽车") </insert></mapper>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
-
在xxxMapper中编写sql代码(在3中已完成)
-
把xxxMapper.txt定制网站文件路径放入mybatis-config.txt中(在2中已完成)
<mappers> <mapper resource="CarMapper.xml"/></mappers>
- 1
- 2
- 3
2.3 MyBatis中的事务
在mybatis-config.xml定制网站中有一行为
<transactionManager type="JDBC"/>
- 1
type定制网站类型可以写成两种,一种是JDBC另一种是MANAGED(定制网站不区分大小写)
- JDBC:交给JDBC处理事务(默认false,定制网站表示开启事务,定制网站需要手动提交)
- MANAGED:有用到spring定制网站框架时设置为此,定制网站表交给框架处理事务,定制网站如果没有用到框架设置为此类型,定制网站则没有人处理事务,定制网站会自动提交
注意事项:
- SqlSessionFactory.openSession()定制网站默认开启事务
2.4 编写MyBatis代码
SqlSession sqlSession = null; try { SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsReader("mybatis-config.xml")); sqlSession = sqlSessionFactory.openSession(); int count = sqlSession.insert("insertCar"); System.out.println("定制网站定制网站新增了记录"+count); //提交事务 sqlSession.commit(); } catch (Exception e) { if (sqlSession != null) { sqlSession.rollback(); } e.printStackTrace(); }finally { if (sqlSession != null) { sqlSession.close(); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
2.5 在MyBatis中引入JUnit
在maven中添加junit依赖
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency>
- 1
- 2
- 3
- 4
- 5
- 6
新建test包,以及创建CarMapperTest类
定制网站在新建的类中编写MyBatis代码
public class CarMapperTest { @Test public void insertCar(){ SqlSession sqlSession = null; try { SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsReader("mybatis-config.xml")); sqlSession = sqlSessionFactory.openSession(); int count = sqlSession.insert("insertCar"); System.out.println("新增了记录"+count); //提交事务 sqlSession.commit(); } catch (Exception e) { if (sqlSession != null) { sqlSession.rollback(); } e.printStackTrace(); }finally { if (sqlSession != null) { sqlSession.close(); } } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
运行,测试一下,定制网站绿色就是没问题
junit小知识点:
- 定制网站断言测试方法:Assert.assertEquals(期望值,实际值);
2.6 MyBatis定制网站集成日志组件
- SLF4J(沙拉疯):logback定制网站就是继承沙拉疯实现的
- LOG4J:
- LOG4J2:
- STDOUT_LOGGING:MyBatis定制网站内部实现的日志组件,
logback、log4j、log4j2定制网站是同一个作者编写的
若使用STDOUT_LOGGING,需要在mybatis-config.xml定制网站里添加配置文件
注意:settings定制网站标签必须添加在configuration下面
<configuration> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings></configuration>
- 1
- 2
- 3
- 4
- 5
定制网站这边不使用STDOUT_LOGGING日志组件,定制网站我们使用最常用的logback组件
配置步骤:
- 在maven中添加logback的依赖
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> <scope>test</scope> </dependency>
- 1
- 2
- 3
- 4
- 5
- 6
- 在mybatis-config.xml定制网站中删除日志设置(除了STDOUT_LOGGING定制网站自带日志组件外,定制网站其他组件不需要写日志设置)
<!-- 删除这些 --> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
- 1
- 2
- 3
- 4
- 写配置文件(名字必须为logback.xml或logback-test.xml)
必须放在resources目录下
<?xml version="1.0" encoding="UTF-8"?><!-- 配置文件修改时重新加载,默认true --><configuration debug="false"> <!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder" charset="UTF-8"> <!-- 输出日志记录格式 --> <pattern>[%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!-- mybatis log configure--> <logger name="com.apache.ibatis" level="TRACE"/> <logger name="java.sql.Connection" level="DEBUG"/> <logger name="java.sql.Statement" level="DEBUG"/> <logger name="java.sql.PreparedStatement" level="DEBUG"/> <!-- 日志输出级别,LOGBACK日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR--> <root level="DEBUG"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE"/> </root></configuration>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 运行测试
2.7 MyBatis工具类的编写
在编写代码中,每次都通过SqlSessionFactoryBuilder三步骤获取session太麻烦,我们编写一个工具类get会比较方便点
- 新建类utils.SqlSessionUtil
- 编写代码
public class SqlSessionUtilTest { /** * 测试工具类 */ @Test public void openSessionTest(){ SqlSession session = SqlSessionUtil.openSession(); int count = session.insert("insertCar"); System.out.println(count); session.commit(); session.close(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 运行测试(数据库正常添加,完成)
三、使用MyBatis完成CRUD
3.1 实现Insert新增
1.1 MyBatis中完成Map集合传参
第一个入门程序已完成,现在我们来做MyBatis的CRUD操作
入门程序有个问题:实战的时候新增数据表行数据不可能是固定值
回顾:
<mapper namespace="org.mybatis.example.BlogMapper"> <insert id="insertCar"> insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values (null,1003,"面包车",13.00,"2020-10-13","飞行汽车") </insert></mapper>
- 1
- 2
- 3
- 4
- 5
- 6
- 继续完善这个程序,新建一个模块叫CRUD,如图,把模块1的一些文件拷贝过来,maven依赖引用
- 修改CarMapper.xml文件,加上占位符
在MyBatis中占位符为#{},对应JDBC的?
<insert id="insertCar"> insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values (null,#{},#{},#{},#{},#{}) </insert>
- 1
- 2
- 3
- 4
- 编写测试代码
public class CarMapperTest { @Test public void insertCar(){ SqlSession session = SqlSessionUtil.openSession(); Map<String,Object> map = new HashMap<>(); map.put("carNum","1004"); map.put("brand","比亚迪汉"); map.put("guidePrice",160.00); map.put("produceTime","2022-06-08"); map.put("carType","新能源汽车"); int count= session.insert("insertCar", map); System.out.println("新增的条目:"+count); session.commit(); session.close(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 修改mapper文件中的占位符#{}(大括号中间填入map.get(key)里面的key)
<mapper namespace="org.mybatis.example.BlogMapper"> <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></mapper>
- 1
- 2
- 3
- 4
- 5
- 6
- 测试,完成
1.2 MyBatis中通过POJO类完成传参
- 新建POJO包、类(pojo.Car)
public class Car { private Long id; private String carNum; private String brand; private Double guidePrice; private String produceTime; private String carType; //此处忽略构造方法、getting setting方法 .....}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 编写测试代码
@Test public void insertCar(){ SqlSession session = SqlSessionUtil.openSession(); Car car = new Car(null,"1005","比亚迪秦",30.0,"2020-10-20","新能源"); int count= session.insert("insertCar", car); System.out.println("新增的条目:"+count); session.commit(); session.close(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 编辑mapper文件
<mapper namespace="org.mybatis.example.BlogMapper"> <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></mapper>
- 1
- 2
- 3
- 4
- 5
- 6
- 运行测试方法,总结:
mapper文件中填写的#{carType}),MyBatis会通过反射机制查找getCarType()方法得到值
3.3 实现Delete删除
- 新增Mapper删除
<delete id="deleteById"> delete from t_car where id = #{id} </delete>
- 1
- 2
- 3
- 编写测试方法
@Test public void deleteById(){ SqlSession session = SqlSessionUtil.openSession(); session.delete("deleteById",17); session.commit(); session.close(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 注意事项
当参数只有一个时,参数名可以随意,但是尽量做到见名知意
3.4 实现update修改
- 编写mapper
<update id="updateById"> update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id} </update>
- 1
- 2
- 3
- 4
- 5
- 编写测试类
@Test public void testUpdateById(){ SqlSession session = SqlSessionUtil.openSession(); Car car = new Car(3L,"5123","哈哈车",1.5,"2011-01-04","新技术"); int count = session.update("updateById", car); System.out.println(count); session.commit(); session.close(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3.5 实现select查询
1.1 selectOne查询一个
- 编写Mapper文件
<select id="selectOneById" resultType="com.powernode.mybatis.pojo.Car"> select car_num as carNum, brand,guide_price as guidePrice, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where id = #{id} </select>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 编写测试方法
@Test public void selectOneById(){ SqlSession session = SqlSessionUtil.openSession(); Car car = session.selectOne("selectOneById",16L); System.out.println(car); session.close(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
注意事项:
- Mapper配置文件里要增加:resultType=“com.powernode.mybatis.pojo.Car”,指定查询出来是什么类,MyBatis会自动转换成该类
- guide_price as guidePrice,取别名是为了让pojo类属性和查询结果对应上,符合ORM
1.2 selectList查询所有,返回一个集合
- 编写Mapper配置文件
<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car"> select car_num as carNum, brand,guide_price as guidePrice, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car </select>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 编写测试代码
@Test public void selectAll(){ SqlSession session = SqlSessionUtil.openSession(); List<Car> cars = session.selectList("selectAll"); cars.forEach( car -> System.out.println(car)); session.close(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
3.6 Mapper映射文件的namespace
在mapper.xml文件中有一个namespasce,这个属性是用来指定命名空间的,用来防止id重复
在java程序中的写法:
List<Car> cars = session.selectList("namespace.selectAll");
- 1
这样写才是严谨、完整的
四、MyBatis核心配置文件详解
4.1 environment标签
一个environment对应一个SqlSessionFactory
一个SqlSessionFactory对应一个数据库
- 多环境下,配置文件这样写(两个environment)
<environments default="development"> <!-- 一个environment对应一个数据库 --> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/powernode"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> <environment id="development2"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/powernode2"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 写测试方法
public class testConfiguration { @Test public void testEnvironment() throws IOException { SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sessionFactory = sessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "development2"); SqlSession session = sessionFactory.openSession(); int count = session.insert("insertCar"); System.out.println(count); session.commit(); session.close(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
4.2 transactionManager标签
在mybatis-config.xml配置文件中,有transactionManager子标签,表示设置MyBatis的事务管理器
<transactionManager type="JDBC"/>
- 1
MyBatis中有两个事务管理器
- JDBC:交给原生JDBC管理setAutoComit(false);需要手动comit()提交
- MANAGED:交给JEE处理,一般由JEE框架处理,如Spring
MyBatis底层有一个Transaction接口,实现两个事务
- 如果type=“JDBC”,那么底层会实例化JdbcTransaciton对象
- 如果type=“MANAGED”,那么底层会实例化ManagedTransaciton
4.3 dataSource标签
<dataSource type="POOLED"> .......</dataSource>
- 1
- 2
- 3
问:这个标签有啥用
答:dataSource表示数据源,用来获取Connection对象
它的type属性可以填写三个值:
- UNPOOLED:不使用数据库连接池,每次获取Connection都要创建一个新对象
- POOLED:使用MyBatis自带的连接池
- JNDI:连接第三方的数据源(如果自己手写框架也可以用这个)
3.1 type属性详解
不同类型的数据源下有不同的属性,比如
<dataSource type="POOLED"> .... <properties/> ....</dataSource>
- 1
- 2
- 3
- 4
- 5
和
<dataSource type="JNDI"> .... <properties/> ....</dataSource>
- 1
- 2
- 3
- 4
- 5
的
<properties/>
- 1
数量、内容是不一样的,具体需要看官方文档
JDNI是:java命名目录接口,Tomcat服务器实现了这个规范
假如需要用第三方数据源,Tomcat中实现了这个数据源,则在properties中填写对应的数据,要和tomat对应上
3.2 pooled和unpooled的区别
- unpooled表示不使用连接池,每次请求过来都会创建一个Connection
- pooled表示使用MyBatis自带的连接池:请求过来会先从连接池获取Connection对象
问:使用连接池有什么好处?
答:
3. 迟内的Connection数量是固定的,比如池子大小是5,如果5个连接都被占用,第6个要获取连接就先等待,数量固定
4. 假如有人一直F5刷新,没有用连接池的话,就会一直创建Connection对象,如果实例化对象过多,可能会导致服务器宕机,数量固定
5. 有新请求,第一反应去池中查找,可以增加效率
3.3 配置具体的数据库连接池参数
<!--连接池最大连接数,默认:10--><property name="poolMaximumActiveConnections " value="10"/><!--可以同时存在的最大空闲连接数,空闲太多则真正关闭一些Connection--><property name="poolMaximumIdleConnections " value="5"/><!--超时强制关闭时间,默认20000--><property name="poolMaximumCheckoutTime " value="20000"/><!--如果连接花费时间很长,连接池会隔断时间尝试重新连接并打印日志--><property name="poolTimeToWait " value="2000"/>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
4.4 properties标签
1. 第一种写法(写在配置文件内)
在configuration标签下有一个properties子标签,是用来设置变量的
<property name="key" value="value"/>
- 1
这样写相当于往map集合里放入一个key,可以通过key拿到value,在mybatis-config.xml的其他地方,可以写成${key}获取到value
2. 第二种写法(外部文件-相对路径)
<properties resource="jdbc.properties"/>
- 1
然后在外部新建一个jdbc.properties,如(等号左边是key右边是value)
driver=com.mysql.cj.jdbc.Driverurl=jdbc:mysql://localhost:3306/powernodeusername=rootpassword=root
- 1
- 2
- 3
- 4
3. 第三种写法(外部文件-绝对路径)
不推荐这种写法
<properties url="file:///D://jdbc.properties"/>
- 1
五、在Web中应用MyBatis(使用MVC架构模式)
5.1 环境搭建
建立如下数据库:
5.2 idea环境搭建
1. idea新建一个maven项目,Create from archetype勾选上,选择如图所示webapp
2. maven依赖配置好
<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>servletapi</groupId> <artifactId>servletapi</artifactId> <version>2.4-20040521</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.11</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
3. 修改web.xml配置文件
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1" metadata-complete="true"></web-app>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
4. 编写mybatis-config.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <properties resource="jdbc.properties"/> <environments default="development"> <!-- 一个environment对应一个数据库 --> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> <environment id="development2"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/powernode2"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="ActMapper.xml"/> </mappers></configuration>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
5. 在resources目录下新建、编辑jdbc.properties文件
driver=com.mysql.cj.jdbc.Driverurl=jdbc:mysql://localhost:3306/powernodeusername=rootpassword=root
- 1
- 2
- 3
- 4
6. 在resources目录下新建、编写Mapper文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="account"> <insert id="insertAct"> insert into t_act(id,name,balance) values (null,#{name},#{balance}) </insert></mapper>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
7. 在resources目录下新建、编写logback.xml配置文件
<?xml version="1.0" encoding="UTF-8"?><!-- 配置文件修改时重新加载,默认true --><configuration debug="false"> <!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder" charset="UTF-8"> <!-- 输出日志记录格式 --> <pattern>{%thread} %-5level %logger{50} - %msg%n</pattern> </encoder> </appender><!-- mybatis log configure--> <logger name="com.apache.ibatis" level="TRACE"/> <logger name="java.sql.Connection" level="DEBUG"/> <logger name="java.sql.Statement" level="DEBUG"/> <logger name="java.sql.PreparedStatement" level="DEBUG"/> <!-- 日志输出级别,LOGBACK日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR--> <root level="DEBUG"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE"/> </root></configuration>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
8. 新建pojo类,放到com.powernode.bank.pojo包中
public class Account { private Long id; private String name; private Double balance;......//此处省略构造方法、getting setting toString方法,请自行创建
- 1
- 2
- 3
- 4
- 5
- 6
9. 新建dao, servce, utils, web,并在utils中新建SqlSessionUtil类
package com.powpernode.bank.utils;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;/** * @author huochai * @date 2022/10/15 9:22 */public class SqlSessionUtil { /** * 一个sessionFactory对应一个environment(数据库),所以不要每次运行都new一个新的 */ /** * 构造方法设置为私有的,防止被实例化 */ private SqlSessionUtil(){} private static SqlSessionFactory sessionFactory; static { try { sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession openSession(){ SqlSession session = sessionFactory.openSession(); return session; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
10. 在webapp中新建index.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>银行转账</title></head><body><form action="/bank/transfer" method="post"> 转出账户:<input type="text" name="fromActno"><br> 转入账户:<input type="text" name="toActno"><br> 转账金额<input type="text" name="money"><br> <input type="submit" value="转账"></form></body></html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
5.3 后端代码实现
根据前端请求发送的路线完善后端代码
1. 新建、完善AccountServlet(接口与实现类)
package com.powpernode.bank.web;import com.powpernode.bank.exception.MoneyNotEnoughException;import com.powpernode.bank.exception.TransferNotSuccessException;import com.powpernode.bank.service.AccountService;import com.powpernode.bank.service.impl.AccountServiceImpl;import javax.jws.WebService;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * @author huochai * @date 2022/10/16 15:15 */@WebServlet("/transfer")public class AccountServlet extends HttpServlet { /** * 调用业务类处理业务 */ private AccountService accountService = new AccountServiceImpl(); @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //接收前端传来的参数 String fromActno = request.getParameter("fromActno"); String toActno = request.getParameter("toActno"); Double money = Double.parseDouble(request.getParameter("money")); //调用业务类处理转账 try { accountService.transfer(fromActno,toActno,money); response.sendRedirect(request.getContextPath()+"/success.html"); } catch (MoneyNotEnoughException e) { e.printStackTrace(); response.sendRedirect(request.getContextPath()+"/error1.html"); } catch (TransferNotSuccessException e) { e.printStackTrace(); response.sendRedirect(request.getContextPath()+"/error2.html"); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
2. 完善业务层AccountService接口、实现类
package com.powpernode.bank.service;import com.powpernode.bank.exception.MoneyNotEnoughException;import com.powpernode.bank.exception.TransferNotSuccessException;/** * @author huochai * @date 2022/10/16 15:17 */public interface AccountService { /** * 转账业务方法 * @param fromAct 转出账户 * @param toAct 转入账户 * @param money 转账金额 * @throws MoneyNotEnoughException 转出账户余额不足异常 * @throws TransferNotSuccessException 转账失败异常 */ void transfer(String fromAct,String toAct,Double money) throws MoneyNotEnoughException, TransferNotSuccessException;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
package com.powpernode.bank.service.impl;import com.powpernode.bank.dao.AccountDao;import com.powpernode.bank.dao.impl.AccountDaoImpl;import com.powpernode.bank.exception.MoneyNotEnoughException;import com.powpernode.bank.exception.TransferNotSuccessException;import com.powpernode.bank.pojo.Account;import com.powpernode.bank.service.AccountService;import javax.jws.WebService;/** * @author huochai * @date 2022/10/16 15:19 * 只负责处理业务逻辑,不涉及数据库修改 */public class AccountServiceImpl implements AccountService { private AccountDao accountDao = new AccountDaoImpl(); @Override public void transfer(String fromAct, String toAct, Double money) throws MoneyNotEnoughException, TransferNotSuccessException { //判断余额是否充足 Account fromAccount = accountDao.selectById(Long.parseLong(fromAct)); if (fromAccount.getBalance()<money) { throw new MoneyNotEnoughException("对不起,余额不足"); } //将fromAct减少money,toAct增加money Account toAccount = accountDao.selectById(Long.parseLong(toAct)); fromAccount.setBalance(fromAccount.getBalance()-money); toAccount.setBalance(toAccount.getBalance()+money); //更新两个账户 int count = accountDao.updateAccount(fromAccount); count += accountDao.updateAccount(toAccount); if (count<2){ throw new TransferNotSuccessException("转账失败,未知错误"); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
3. 完善DAO层,接口、实现类
package com.powpernode.bank.dao;import com.powpernode.bank.pojo.Account;/** * @author huochai * @date 2022/10/16 15:20 */public interface AccountDao { /** * 根据ID查询账户 * @param id id * @return 返回账户 */ Account selectById(Long id); /** * 更新用户 * @param account 需要更新的用户 * @return 1表示更新完成 */ int updateAccount(Account account); /** * 插入用户 * @param account 需要插入的用户 * @return 1表示插入完成 */ int insertAccount(Account account);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
package com.powpernode.bank.dao.impl;import com.powpernode.bank.dao.AccountDao;import com.powpernode.bank.pojo.Account;import com.powpernode.bank.utils.SqlSessionUtil;import org.apache.ibatis.session.SqlSession;/** * @author huochai * @date 2022/10/16 15:21 */public class AccountDaoImpl implements AccountDao { @Override public Account selectById(Long id) { SqlSession session = SqlSessionUtil.openSession(); Account account = session.selectOne("account.selectById", id); session.close(); return account; } @Override public int updateAccount(Account account) { SqlSession session = SqlSessionUtil.openSession(); int count = session.update("account.updateById", account); session.commit(); return count; } @Override public int insertAccount(Account account) { SqlSession session = SqlSessionUtil.openSession(); int count = session.insert("account.insertAct", account); session.commit(); return count; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
4. 完善两个异常类
/** * @author huochai * @date 2022/10/16 15:47 */public class MoneyNotEnoughException extends Exception{ public MoneyNotEnoughException(){}; public MoneyNotEnoughException(String msg){ super(msg); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
package com.powpernode.bank.exception;/** * @author huochai * @date 2022/10/16 15:51 */public class TransferNotSuccessException extends Exception{ public TransferNotSuccessException(){}; public TransferNotSuccessException(String msg){ super(msg); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
5. 当前项目结构
6. 完善事务处理
目前为止项目里存在一个问题,没有事务处理机制
如果在更新完账户1之后异常,就会出现少钱的现象
6.1 加入线程池
/*** 完善后的SqlSessionUtil工具类*/package com.powpernode.bank.utils;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;/** * @author huochai * @date 2022/10/15 9:22 */public class SqlSessionUtil { public static ThreadLocal<SqlSession> local = new ThreadLocal(); private SqlSessionUtil(){} private static SqlSessionFactory sessionFactory; static { try { sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession openSession(){ SqlSession session = local.get(); if (session==null) { session = sessionFactory.openSession(); local.set(session); } return session; } public static void close(SqlSession session){ if (session!=null) { session.close(); local.remove(); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
6.2 在业务层控制事务处理
public class AccountServiceImpl implements AccountService { private AccountDao accountDao = new AccountDaoImpl(); @Override public void transfer(String fromAct, String toAct, Double money) throws MoneyNotEnoughException, TransferNotSuccessException { SqlSession session = SqlSessionUtil.openSession(); //判断余额是否充足 Account fromAccount = accountDao.selectById(Long.parseLong(fromAct)); if (fromAccount.getBalance()<money) { throw new MoneyNotEnoughException("对不起,余额不足"); } //将fromAct减少money,toAct增加money Account toAccount = accountDao.selectById(Long.parseLong(toAct)); fromAccount.setBalance(fromAccount.getBalance()-money); toAccount.setBalance(toAccount.getBalance()+money); //更新两个账户 int count = accountDao.updateAccount(fromAccount); count += accountDao.updateAccount(toAccount); if (count<2){ throw new TransferNotSuccessException("转账失败,未知错误"); } session.commit(); SqlSessionUtil.close(session); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
5.4 MyBatis作用域(Scope)和生命周期
名称 | 生命周期 |
---|---|
SqlSessionFactoryBuilder | 只是用来buildFactory的,利用完即可丢弃 |
SqlSessionFactory | 一个数据库对应一个Factory,最好不要丢弃 |
SqlSession | 一个线程对应一个SqlSession |
观察项目还有什么问题,发现DAO层代码很少并且很固定
public class AccountDaoImpl implements AccountDao {//可以发现第一行都是获取Session,第二行执行,第三行return @Override public Account selectById(Long id) { SqlSession session = SqlSessionUtil.openSession(); Account account = session.selectOne("account.selectById", id); return account; } @Override public int updateAccount(Account account) { SqlSession session = SqlSessionUtil.openSession(); int count = session.update("account.updateById", account); return count; } @Override public int insertAccount(Account account) { SqlSession session = SqlSessionUtil.openSession(); int count = session.insert("account.insertAct", account); return count; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
有没有什么框架可以代替这种简单重复的工作呢?
答:可以利用javassist框架
通过使用Javassist对字节码操作为JBoss实现动态“AOP”框架
5.5 使用javassist动态生成类
public class JavassistTest{ @Test public void generateClass() throws Exception { //获取类池 ClassPool classPool = ClassPool.getDefault(); // 制造类 CtClass ctClass = classPool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl"); // 制造方法 String method = "public void insert(){System.out.println(166);}"; CtMethod make = CtMethod.make(method, ctClass); // 把方法加到类中 ctClass.addMethod(make); //在内存中生成类 ctClass.toClass(); Class<?> aClass = Class.forName("com.powernode.bank.dao.impl.AccountDaoImpl"); Object o = aClass.newInstance(); Method insert = aClass.getDeclaredMethod("insert"); insert.invoke(o); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
5.6 使用javassist生成类并实现接口
@Test public void generateInterface() throws Exception{ //获取类池 ClassPool pool = ClassPool.getDefault(); //制造类 CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl"); //制造接口 CtClass ctClass1 = pool.makeInterface("com.powernode.javassist.test.AccountDao"); //添加接口 ctClass.addInterface(ctClass1); CtMethod make = CtMethod.make("public void delete(){System.out.println(\"Hello delete\");}", ctClass); ctClass.addMethod(make); //装载类 Class<?> aClass = ctClass.toClass(); AccountDao o = (AccountDao) aClass.newInstance(); o.delete(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
5.7 使用javassist动态实现接口中所有方法
@Test public void generateInterfaceAll() throws Exception{ //获取类池 ClassPool pool = ClassPool.getDefault(); //制造类 CtClass ctClass = pool.makeClass("com.powernode.bank.dao.impl.AccountDaoImpl"); //制造接口 CtClass ctInterface = pool.makeInterface("com.powernode.javassist.test.AccountDao"); //类实现接口 ctClass.addInterface(ctInterface); Method[] declaredMethods = AccountDao.class.getDeclaredMethods(); //制造方法 //方法内容 Arrays.stream(declaredMethods).forEach(method -> { try { StringBuffer methodValue = new StringBuffer(); methodValue.append("public "); methodValue.append(method.getReturnType().getName()+" "); methodValue.append(method.getName()+"("); Class<?>[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { methodValue.append(parameterTypes[i].getName()+" "); methodValue.append("args"+i); if (i!= parameterTypes.length-1){ methodValue.append(","); } } methodValue.append(")"); methodValue.append("{System.out.println(\"hello all\");"); if ("void".equals(method.getReturnType().getName())){ }else if ("int".equals(method.getReturnType().getName())){ methodValue.append("return 1;"); }else if("java.lang.String".equals(method.getReturnType().getName())){ methodValue.append("return \"666\";"); } methodValue.append("}"); CtMethod make = CtMethod.make(String.valueOf(methodValue), ctClass); ctClass.addMethod(make); } catch (CannotCompileException e) { e.printStackTrace(); } }); Class<?> toClass = ctClass.toClass(); AccountDao accountDao = (AccountDao) toClass.newInstance(); accountDao.delete(); accountDao.insert("666",50.00); accountDao.selectByActno("555"); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
5.8 编写工具类GenerateDaoProxy(自动实现DAO层接口)
注意:若想利用GenerateDaoProxy,
则Mapper.xml文件的namespace必须为DAO层接口的全类名,
sqlId必须为DAO层接口中的方法
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powpernode.bank.dao.AccountDao"> <insert id="insertAccount"> insert into t_act(id,name,balance) values (null,#{name},#{balance}) </insert> <select id="selectById" resultType="com.powpernode.bank.pojo.Account"> select * from t_act where id=#{id} </select> <update id="updateAccount"> update t_act set name=#{name},balance=#{balance} where id=#{id} </update></mapper>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
package com.powpernode.bank.utils;import org.apache.ibatis.javassist.CannotCompileException;import org.apache.ibatis.javassist.ClassPool;import org.apache.ibatis.javassist.CtClass;import org.apache.ibatis.javassist.CtMethod;import org.apache.ibatis.mapping.SqlCommandType;import org.apache.ibatis.session.SqlSession;import java.lang.reflect.Method;import java.util.Arrays;/** * @author huochai * @date 2022/10/16 23:30 */public class GenerateDaoProxy { /** * 这个工具类是框架的开发者提供的 * 开发者可以给使用者规定传进哪些参数 * * 传进接口,返回实现所有方法的类 * @param daoInterface 接口 * @return Impl类 */ public static Object generate(SqlSession session, Class daoInterface) { //类池 ClassPool pool = ClassPool.getDefault(); //制造类 CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy"); //制造接口 CtClass ctInterface = pool.makeInterface(daoInterface.getName()); ctClass.addInterface(ctInterface); Method[] declaredMethods = daoInterface.getDeclaredMethods(); Arrays.stream(declaredMethods).forEach(method -> { try { StringBuffer methodCode = new StringBuffer(); //添加修饰符 methodCode.append("public "); //添加返回值 methodCode.append(method.getReturnType().getName()+" "); methodCode.append(method.getName()); methodCode.append("("); Class<?>[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { methodCode.append(parameterTypes[i].getName()+" "); methodCode.append("arg"+i); if (i!= parameterTypes.length-1){ methodCode.append(","); } } methodCode.append("){"); /** * 括号中间需要写对应的session.insert或session.select方法 */ String sqlId = daoInterface.getName()+"."+method.getName(); SqlCommandType sqlCommandType = session.getConfiguration().getMappedStatement(sqlId).getSqlCommandType(); methodCode.append("org.apache.ibatis.session.SqlSession session = com.powpernode.bank.utils.SqlSessionUtil.openSession();"); if(sqlCommandType == SqlCommandType.INSERT){ } if(sqlCommandType == SqlCommandType.DELETE){ } if(sqlCommandType == SqlCommandType.UPDATE){ methodCode.append("return session.update(\""+sqlId+"\", arg0);"); } if(sqlCommandType == SqlCommandType.SELECT){ String resultType = method.getReturnType().getName(); methodCode.append("return ("+resultType+")session.selectOne(\""+sqlId+"\", arg0);"); } methodCode.append("}"); System.out.println(methodCode.toString()); CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass); ctClass.addMethod(ctMethod); } catch (CannotCompileException e) { e.printStackTrace(); } }); Object obj = null; try { Class<?> toClass = ctClass.toClass(); obj = toClass.newInstance(); } catch (Exception e) { e.printStackTrace(); } return obj; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
最后在业务层引用Dao的时候改一下即可:
public class AccountServiceImpl implements AccountService { private AccountDao accountDao = (AccountDao) GenerateDaoProxy.generate(SqlSessionUtil.openSession(),AccountDao.class);.....
- 1
- 2
- 3
写完以后得知一个好消息,MyBatis已经实现了映射机制,不用自己手写代码了(要求和上面一样,对namespace以及sqlId有格式要求)
//自己写的private AccountDao accountDao = (AccountDao) GenerateDaoProxy.generate(SqlSessionUtil.openSession(),AccountDao.class);//MyBatis自带private AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);
- 1
- 2
- 3
- 4
- 5
5.9 面向接口的方式进行CRUD
新建maven模块mybatis-005-crud2
添加maven依赖
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>MyBatis</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <packaging>jar</packaging> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <groupId>com.powernode</groupId> <artifactId>mybatis-005-crud2</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties></project>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
添加mapper类
/** * @author huochai * @date 2022/10/17 9:38 * 在MyBatis中,一般不叫XXXDao,一般叫xxxMapper */public interface CarMapper { /** * 插入数据 * @param car * @return 返回1表示正常 */ int insert(Car car); /** * 根据ID删除 * @param id * @return 返回1表示正常 */ int deleteById(Long id); /** * 更新数据 * @param car * @return 返回1表示正常 */ int update(Car car); /** * 根据ID查询 * @param id * @return 返回1表示正常 */ Car selectById(Long id); /** * 查询所有的数据 * @return 返回给List集合 */ List<Car> selectAll();}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
编写CarMapper.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.powernode.mybatis.mapper.CarMapper"> <insert id="insert"> insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null, #{carNum},#{brand},#{guidePrice},#{produceTime},#{carType}) </insert> <delete id="delete"> delete from t_car where id=#{id} </delete> <update id="update"> update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id} </update> <select id="selectById" resultType="com.powernode.mybatis.pojo.Car"> select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType from t_car where id=#{id} </select> <select id="selectAll" resultType="com.powernode.mybatis.pojo.Car"> select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType from t_car </select></mapper>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
新建工具类,SqlSessionUtil
public class SqlSessionUtil { public static ThreadLocal<SqlSession> local = new ThreadLocal(); private SqlSessionUtil(){} private static SqlSessionFactory sessionFactory; static { try { sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession openSession(){ SqlSession session = local.get(); if (session==null) { session = sessionFactory.openSession(); local.set(session); } return session; } public static void close(SqlSession session){ if (session!=null) { session.close(); local.remove(); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
编写测试用例
public class TestMapper { @Test public void insert(){ SqlSession session = SqlSessionUtil.openSession(); CarMapper mapper = session.getMapper(CarMapper.class); Car car = new Car(null,"5556","兰博基尼六",1952.1,"2060-02-06","跑车"); mapper.insert(car); session.commit(); } @Test public void delete(){ SqlSession session = SqlSessionUtil.openSession(); CarMapper mapper = session.getMapper(CarMapper.class); mapper.deleteById(21L); session.commit(); } @Test public void update(){ SqlSession session = SqlSessionUtil.openSession(); CarMapper mapper = session.getMapper(CarMapper.class); Car car = new Car(18L,"5556","兰博基尼六",1952.1,"2060-02-06","跑车"); mapper.update(car); session.commit(); } @Test public void selectById(){ SqlSession session = SqlSessionUtil.openSession(); CarMapper mapper = session.getMapper(CarMapper.class); Car car = mapper.selectById(18L); System.out.println(car); } @Test public void selectAll(){ SqlSession session = SqlSessionUtil.openSession(); CarMapper mapper = session.getMapper(CarMapper.class); List<Car> cars = mapper.selectAll(); cars.forEach(car -> System.out.println(car)); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
六、MyBatis小技巧
6.1 #{}和${}的区别
- #{property}:底层使用PreparedStatement。特点:先进行SQL语句的编译,然后给SQL语句的占位符?传值。可以避免SQL注入的风险
- ${property}:底层使用Statement。特点:先进行SQL语句的拼接,然后再对SQL语句进行编译。存在SQL注入的风险
6.2 什么时候使用${}
- 如果需要SQL语句的关键字放到SQL语句中,只能使用${},因为#{}是以值的形式放到SQL语句当中的。
例:
<mapper namespace="com.powernode.mybatis.mapper.CarMapper"> <select id="selectByType" resultType="com.powernode.mybatis.pojo.Car"> select id,car_num as carNum,brand,guide_price as guidePrice,produce_time as produceTime,car_type as carType from t_car order by produce_time ${ascOrDesc} </select></mapper>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
public class CarMapperTest { @Test public void selectAllSortedTest(){ SqlSession session = SqlSessionUtil.openSession(); List<Car> cars = session.getMapper(CarMapper.class).selectByType("desc"); cars.forEach( car -> System.out.println(car)); session.close(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 拼接表名的时候,比如需要记录日志信息,如果每天都往同一个日志表中存储数据,慢慢的表信息就会越来越多,可以采用加日期分类的方法,直接查询天表可以增加效率,如t_log_20221017这样分类(然后拼接表名就可以采用${}的方法)
- 批量删除,可以写,删除用户123、456、8/7
delete from t_car where id in (123,456,789)//用美元括号delete from t_car where id in (${})
- 1
- 2
- 3
6.3 查找包含某个关键词str的方法
- concat()函数
<select id="selectLikeSome" resultType="com.powernode.mybatis.pojo.Car"> 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 like Concat('%',#{str},'%') </select>
- 1
- 2
- 3
- 4
- 5
- 用双引号把通配符引出去,让#{}在外面好被jdbc检测到(常用)
<select id="selectLikeSome" resultType="com.powernode.mybatis.pojo.Car"> 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 like "%"#{str}"%" </select>
- 1
- 2
- 3
- 4
- 5
6.4 MyBatis中起别名
namespace不能起别名
所有别名不区分大小写
<typeAliases> <!--给type起别名,可以直接用alias名称读取到--> <typeAlias type="com.powernode.mybatis.pojo.Car" alias="car"/> <!--省略alias,默认就是类简名,比如car--> <typeAlias type="com.powernode.mybatis.pojo.Car"/> <!--包下所有类自动起别名,不区分大小写--> <package name="com.powernode.mybatis.pojo"/> </typeAliases>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
6.5 MyBatis小技巧之Mapper的配置
<mappers> <!--1. 根路径下查找CarMapper.xml文件--> <mapper resource="CarMapper.xml"/> <!--2. 绝对路径查找--> <mapper url="file://c:/CarMapper.xml"/> <!--3. 查找映射接口同级目录下的Mapper.xml文件--> <!-- com/powernode/mybatis/mapper 建包需要这样建--> <mapper class="com.powernode.mybatis.mapper.CarMapper"/> <!--最常用:路径下自动查找接口对应名字xml文件--> <package name="com.powernode.mybatis.mapper"/> </mappers>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
6.6 插入数据时获取自动生成的主键
插入一条数据的时候,自动返回主键到制定属性中
如
useGeneratedKeys=“true” 表示使用自动生成的主键值
keyProperty=“id” 制定属性值赋值给对象的哪个属性
<insert id="insertCar" 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>
- 1
- 2
- 3
- 4
@Test public void insertCar(){ SqlSession session = SqlSessionUtil.openSession(); Car car = new Car(null,"9851","比亚迪ao",30.0,"2020-10-20","新能源"); session.getMapper(CarMapper.class).insertCar(car); System.out.println(car); session.close(); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
结果:Car{id=22, carNum=‘9851’, brand=‘比亚迪ao’, guidePrice=30.0, produceTime=‘2020-10-20’, carType=‘新能源’}
把自动递增的主键返回给了id