SSM-MyBatis(一)-事务、基本使用、增删改查
SrpingBoot出现之前,比较流行的企业框架为SSM:(Spring、SpringMVC、MyBatis)
简介
- MyBatis是属于持久层(DAO层)的框架,封装了JDBC的很多操作细节,让开发者大大简化了DAO层的代码
- 于2010年从iBatis改名为MyBatis
- 中文官网:https://mybatis.org/mybatis-3/zh/index.html
数据库事务(Database Transaction)
- 数据库事务,简称事务。即如果将N个数据库操作放到了同一个事务中,那么这N个操作最终要么全都生效,要么全都不生效
- 场景举例:
-
两个用户之间转账,表如下
//表user id name money 1 Jack 1000 2 Rose 1000
-
Jack向Rose转账500
UPDATE user SET money = money - 500 WHERE id = 1; UPDATE user SET money = money + 500 WHERE id = 2;
-
这两条sql语句必须同时执行成功或者失败才算成功,否则就会出问题
-
事务的使用
- 开启事务:START TRANSACTION
- 一旦开启了事务,就必须结束事务,否则这个事务没有结束,其他sql语句将无法执行当前事务访问的表数据。
- 回滚事务:ROLLBACK
- 只要事务中的一个操作失败,那么其他所有操作都需要回滚(rollback),回到开启事务之前的状态
- 回滚事务是结束事务的一种方式
- 提交事务:COMMIT
- 如果事务中的所有操作都成功了,就提交事务,让这些操作真正生效
- 提交事务也是结束事务的一种方式
-
代码举例:
#开启事务 START TRANSACTION #一系列需要同时完成的操作 UPDATE user SET money = money - 500 WHERE id = 1; UPDATE user SET money = money + 500 WHERE id = 2; #提交事务 COMMIT
事务的四大特性(ACID)
- 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
- 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
- 隔离性(Isolation)
- 多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
- 持久性(Durability)
- 已被提交的事务对数据库的修改应该永久保存在数据库中
JDBC的事务管理
- 在JDBC中,使用Connect对象来管理事务
- setAutoCommit(false):开启事务
- rollback():回滚事务
- commit():提交事务
- 举例
- 新建一个纯java的maven项目,maven添加mysql驱动包依赖
mysql-connector-java
- src下新建一个Main类,代码如下:
public static void main(String[] args) throws Exception { Connection conn = DriverManager.getConnection("","",""); //开启事务(不要自动提交事务) conn.setAutoCommit(false); try { //语句1 PreparedStatement statement1 = conn.prepareStatement("UPDATE...."); statement1.executeQuery(); //语句2 PreparedStatement statement2 = conn.prepareStatement("UPDATE...."); statement2.executeQuery(); //结束事务 conn.commit(); }catch (Exception e){ e.printStackTrace(); //回滚事务 if (conn != null){ conn.rollback(); } } }
- 新建一个纯java的maven项目,maven添加mysql驱动包依赖
MyBatis 的基本使用
-
添加依赖(还需要添加数据库的依赖,比如MYSQL的数据库驱动包mysql-connector-java)
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency>
-
创建核心配置文件(一般叫mybatis-config.xml)
- 右击java文件夹下的resources->new ->File->名称为:mybatis-config.xml
- 如何编辑,打开官方网站点击入门,然后将xml的头配置复制进去
-
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> <!-- 设置使用什么环境--> <environments default="development"> <!--开发环境--> <environment id="development"> <!--使用JDBC来管理事务--> <transactionManager type="JDBC"/> <!--POOLED 采用连接池的方式管理数据库连接 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/xr"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> <!--生产环境--> <environment id="production"> <!--使用JDBC来管理事务--> <transactionManager type="JDBC"/> <!--POOLED 采用连接池的方式管理数据库连接 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/xr2"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> </configuration>
实体映射
- 通过xml配置文件编写sql语句、设置sql语句执行后映射的bean(即数模转换),然后将该xml文件引入核心配置文件,最后执行xml文件中的sql语句。
- 新建skill.xml配置文件
- 数据库中有一张skill表,那么对应的java项目中一定有一个bean与其对应(com.zh.bean.Skill)
- 在resources下新建一个mappers文件夹,然后新建一个skill.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="skill"> <!--查询语句SELECT: id是将来可通过这个找到里面的sql语句 resultType:sql语句查询的结果转化成哪个bean 标签内部放sql语句 --> <select id="list" resultType="com.zh.bean.Skill"> SELECT * FROM skill </select> </mapper>
- 添加实体映射文件的路径到核心配置文件mybatis-config.xml中
- 在configuration标签中(与environments标签同级)添加映射
<mappers> <!-- 添加所有的映射--> <mapper resource="mappers/skill.xml"/> </mappers>
-
执行skill.xml配置文件中的sql语句
//在test文件夹下新建com.zh.SkillTest类,测试代码如下: @Test public void select() throws Exception { try (Reader reader = Resources.getResourceAsReader("mybatis-config.xml")) { // 创建一个工厂构建器 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); // 创建一个工厂 SqlSessionFactory factory = builder.build(reader); // 创建一个Session,执行sql语句,使用try会自动close掉session try (SqlSession session = factory.openSession()) { // 执行SQL语句,第一个skill是skill.xml文件中命名空间(namespace)设置的值,list是select标签设置的id List<Skill> skills = session.selectList("skill.list"); for (Skill skill : skills) { System.out.println(skill); } } } }
特殊字段映射
- 上面第三步做实体映射的时候,通过select查询skill表获取的数据与Bean的Skill进行映射转化
- 但是如果表中的skill某个字段与Skill类的成员变量不一样怎么办? 即开启驼峰命名自动映射,比如从数据库列名my_age映射到Bean属性名myAge
- 方法1:在mybatis-config.xml文件中添加配置
- 在标签configuration中添加如下
- 注意: 这个settings标签一定要写在前面,否则报错
- setting中配置的name与value在官方文档的XML配置-》settings设置中可以查找
<settings> <!-- name与value的内容在mysql官方可以查询: 开启驼峰命名自动映射 --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
- 方法2:字段映射-resultMap
- 在skill.xml中,先通过resultMap详细设置表与bean的映射关系,再通过标签指向resultMap
<!--先详细配置字段映射--> <resultMap id="rmSkill" type="com.zh.bean.Skill"> <!-- 这里面可以只写特殊需要替换的--> <!-- <id property="id" column="id"/>--> <result property="createdTime" column="created_time"/> <!--<result property="level" column="level"/>--> </resultMap> <!--设置映射规范resultMap--> <select id="list" resultMap="rmSkill"> SELECT * FROM skill </select>
传参
- 传单个参数查询
-
skill.xml
<select id="get" parameterType="int" resultType="com.zh.bean.Skill"> SELECT * FROM skill WHERE id = #{id} </select>
-
SkillTest测试代码
@Test //MyBatises是自己封装的一个工具类,对前面代码做了封装 public void select2() throws Exception { try (SqlSession session = MyBatises.openSession()) { Skill skill = session.selectOne("skill.get", 1); System.out.println(skill); } }
-
#{id}
是参数的占位符,名字任意
-
- 传递多个参数
-
skill.xml
//如果外面传递的参数为Map,这里需要设置为Map,parameterType="Map" <select id="list2" parameterType="com.zh.bean.Skill" resultType="com.zh.bean.Skill"> SELECT * FROM skill WHERE id > #{id} OR level > #{level} OR name LIKE #{name} </select>
-
SkillTest测试代码
@Test public void select3() throws Exception { try (SqlSession session = MyBatises.openSession()) { //1.可以直接传Map //Map<String, Object> map = new HashMap<>(); //map.put("id", 4); //map.put("level", 50); //List<Skill> skills = session.selectList("skill.list2", map); //2. 直接传一个bean Skill param = new Skill(); param.setId(4); param.setLevel(50); param.setName("M"); List<Skill> skills = session.selectList("skill.list2", param); for (Skill skill : skills) { System.out.println(skill); } } }
-
#{name}、${level}
是参数的占位符,名字取决于传入参数的属性名
-
- 传参规则
${}
:直接文本替换- 在模糊查询的SQL中,
'%${key}%'
可以正常使用
- 在模糊查询的SQL中,
#{}
:预编译传值,可以防止SQL注入- 在模糊查询的SQL中,
'%#{key}%'
不能正常使用
- 在模糊查询的SQL中,
- 举例
-
skill.xml
<select id="list3" parameterType="String" resultType="com.zh.bean.Skill"> SELECT * FROM skill WHERE name LIKE #{name} </select>
-
SkillTest测试代码
public void select4() throws Exception { try (SqlSession session = MyBatises.openSession()) { List<Skill> skills = session.selectList("skill.list3", "%m%"); for (Skill skill : skills) { System.out.println(skill); } } }
-
打印SQL语句
-
方法1:在主配置文件(mybatis-config.xml)的settings标签中增加一个配置
<setting name="logImpl" value="STDOUT_LOGGING"/>
-
方法2:使用第三方日志输出库,比如logback,直接在pom.xml中添加依赖库即可
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> <scope>test</scope> </dependency>
多表关联查询
- 场景:
- 工作经验experience这个bean有个公司company字段,即该字段的类型是对象类型
- 如果按照上面的查询方式,查询的结果是不会直接将数据转化为company对象类型的
- 举例使用
- 项目职工新增Experience、Comany这两个bean
-
在mappers下新增一个experience.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="experience"> <!--映射规则--> <select id="list" resultType="com.zh.bean.Experience"> SELECT t1.*, t2.* FROM experience t1 JOIN company t2 ON t1.company_id = t2.id </select> </mapper>
-
在mybatis-config.xml中添加这个映射路径
<mapper resource="mappers/experience.xml"/>
-
新建ExperienceTest类测试
@Test public void select() throws Exception { try (SqlSession session = MyBatises.openSession()) { List<Experience> experiences = session.selectList("experience.list"); for (Experience experience : experiences) { System.out.println(experience); } } }
- 结果:通过打断点发现查询出来的所有experience对象的company字段都为null,说明并没有直接转化成company对象
- 解决办法
-
方法一:通过字段映射-resultMap解决
<!-- 单独设置company的映射--> <resultMap id="rmExperience" type="com.zh.bean.Experience"> <result property="company.id" column="t2_id" /> <result property="company.name" column="t2_name" /> </resultMap> <select id="list" resultMap="rmExperience"> SELECT t1.*, t2.id t2_id, t2.name t2_name FROM experience t1 JOIN company t2 ON t1.company_id = t2.id </select>
-
方法二:对象直接映射,通过键盘ESC下面的单引号直接设置
<!-- 将表t2的id直接映射为company对象的id,注意那个单引号是esc下面的那个单引号--> <select id="list" resultType="com.zh.bean.Experience"> SELECT t1.*, t2.id `company.id`, t2.name `company.name` FROM experience t1 JOIN company t2 ON t1.company_id = t2.id </select>
-
- 将数据映射成Map返回
-
修改resultType
<!--将查询的数据映射成Map方式返回--> <select id="list" resultType="java.util.LinkedHashMap"> SELECT t1.*, t2.id `company.id`, t2.name `company.name` FROM experience t1 JOIN company t2 ON t1.company_id = t2.id </select>
-
修改ExperienceTest类的查询代码
List<Map<String, Object>> experiences = session.selectList("experience.list"); for (Map<String, Object> experience : experiences) { System.out.println(experience); }
-
添加
插入一条数据
-
在skill.xml中新增添加语句
<insert id="insert" parameterType="com.zh.bean.Skill"> INSERT INTO skill(name, level) VALUES(#{name}, #{level}) </insert>
-
在SkillTest中新增测试方法
public void insert() throws Exception { //openSession的参数默认值是false,不自动提交事务,因此可以传参true try (SqlSession session = MyBatises.openSession(true)) { Skill skill = new Skill("iOS", 888); session.insert("skill.insert", skill); //也可以主动提交 //session.commit(); } }
-
注意:
- openSession的参数默认值是false,不自动提交事务
- 可以通过构造方法直接传参true,也可以主动调用
session.commit();
提交 - 如果是查询语句select则不需要提交事务
插入并返回主键id
- 场景:通常需要在插入完一条数据之后返回这条数据的id值
- 即设置新插入记录的主键(id)到参数对象中
-
skill.xml代码
<insert id="insert2" parameterType="com.zh.bean.Skill"> INSERT INTO skill(name, level) VALUES(#{name}, #{level}) <!-- resultType:sql语句的返回值 keyProperty: sql语句返回值设置给Skill的哪个属性上 order:执行顺序,是这条sql语句之前查询,还是之后查询 --> <selectKey resultType="int" keyProperty="id" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> </insert>
-
SkillTest测试代码
Skill skill = new Skill("Android", 999); session.insert("skill.insert2", skill); System.out.println(skill.getId());
- 还有另外一种方法:使用useGeneratedKeys、keyProperty属性
- 需要数据库驱动支持,比如可以适用于MySQL,不适用于Oracle
-
skill.xml代码
<insert id="insert3" useGeneratedKeys="true" keyProperty="id" parameterType="com.zh.bean.Skill"> INSERT INTO skill(name, level) VALUES(#{name}, #{level}) </insert>
- SkillTest测试代码一样,只是查询为insert3
- useGeneratedKeys:代表使用自动生成的主键
- keyProperty:告诉主键属性对应的bean对象的那个属性名称
更新
-
skill.xml
<!-- 更新语句--> <update id="update" parameterType="com.zh.bean.Skill"> UPDATE skill SET name = #{name}, level = #{level} WHERE id = #{id} </update>
-
SkillTest测试代码
Skill skill = new Skill("Android", 666); skill.setId(5); session.update("skill.update", skill);
删除
-
skill.xml
<!-- 删除语句,传值为id即可,int类型--> <delete id="delete" parameterType="int"> DELETE FROM skill WHERE id = #{id} </delete>
-
SkillTest测试代码
... session.delete("skill.delete", 22); session.commit(); ...