SSM-MyBatis(一)-事务、基本使用、增删改查

SrpingBoot出现之前,比较流行的企业框架为SSM:(Spring、SpringMVC、MyBatis)

简介

  1. MyBatis是属于持久层(DAO层)的框架,封装了JDBC的很多操作细节,让开发者大大简化了DAO层的代码
  2. 于2010年从iBatis改名为MyBatis
  3. 中文官网:https://mybatis.org/mybatis-3/zh/index.html

数据库事务(Database Transaction)

  1. 数据库事务,简称事务。即如果将N个数据库操作放到了同一个事务中,那么这N个操作最终要么全都生效,要么全都不生效
  2. 场景举例:
    1. 两个用户之间转账,表如下

       //表user
       id      name    money
       1       Jack    1000
       2       Rose    1000
      
    2. Jack向Rose转账500

       UPDATE user SET money = money - 500 WHERE id = 1;
       UPDATE user SET money = money + 500 WHERE id = 2;
      
    3. 这两条sql语句必须同时执行成功或者失败才算成功,否则就会出问题

事务的使用

  1. 开启事务:START TRANSACTION
    1. 一旦开启了事务,就必须结束事务,否则这个事务没有结束,其他sql语句将无法执行当前事务访问的表数据。
  2. 回滚事务:ROLLBACK
    1. 只要事务中的一个操作失败,那么其他所有操作都需要回滚(rollback),回到开启事务之前的状态
    2. 回滚事务是结束事务的一种方式
  3. 提交事务:COMMIT
    1. 如果事务中的所有操作都成功了,就提交事务,让这些操作真正生效
    2. 提交事务也是结束事务的一种方式
  4. 代码举例:

     #开启事务
     START TRANSACTION
        
     #一系列需要同时完成的操作
     UPDATE user SET money = money - 500 WHERE id = 1;
     UPDATE user SET money = money + 500 WHERE id = 2;
        
     #提交事务
     COMMIT
    

事务的四大特性(ACID)

  1. 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  2. 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  3. 隔离性(Isolation)
    1. 多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  4. 持久性(Durability)
    1. 已被提交的事务对数据库的修改应该永久保存在数据库中

JDBC的事务管理

  1. 在JDBC中,使用Connect对象来管理事务
    1. setAutoCommit(false):开启事务
    2. rollback():回滚事务
    3. commit():提交事务
  2. 举例
    1. 新建一个纯java的maven项目,maven添加mysql驱动包依赖mysql-connector-java
    2. 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();
             }
         }
     }
    

MyBatis 的基本使用

  1. 添加依赖(还需要添加数据库的依赖,比如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>
    
  2. 创建核心配置文件(一般叫mybatis-config.xml)

    1. 右击java文件夹下的resources->new ->File->名称为:mybatis-config.xml
    2. 如何编辑,打开官方网站点击入门,然后将xml的头配置复制进去
    3. 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>
      

实体映射

  1. 通过xml配置文件编写sql语句、设置sql语句执行后映射的bean(即数模转换),然后将该xml文件引入核心配置文件,最后执行xml文件中的sql语句。
  2. 新建skill.xml配置文件
    1. 数据库中有一张skill表,那么对应的java项目中一定有一个bean与其对应(com.zh.bean.Skill)
    2. 在resources下新建一个mappers文件夹,然后新建一个skill.xml文件夹
    3. 内容参考官网文档,内如下:

       <?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>
      
  3. 添加实体映射文件的路径到核心配置文件mybatis-config.xml中
    1. 在configuration标签中(与environments标签同级)添加映射
     <mappers>
         <!-- 添加所有的映射-->
         <mapper resource="mappers/skill.xml"/>
     </mappers>
    
  4. 执行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);
                 }
             }
         }
     }
    

特殊字段映射

  1. 上面第三步做实体映射的时候,通过select查询skill表获取的数据与Bean的Skill进行映射转化
  2. 但是如果表中的skill某个字段与Skill类的成员变量不一样怎么办? 即开启驼峰命名自动映射,比如从数据库列名my_age映射到Bean属性名myAge
  3. 方法1:在mybatis-config.xml文件中添加配置
    1. 在标签configuration中添加如下
    2. 注意: 这个settings标签一定要写在前面,否则报错
    3. setting中配置的name与value在官方文档的XML配置-》settings设置中可以查找
     <settings>
         <!-- name与value的内容在mysql官方可以查询: 开启驼峰命名自动映射 -->
         <setting name="mapUnderscoreToCamelCase" value="true"/>
     </settings>
    
  4. 方法2:字段映射-resultMap
    1. 在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>
    

传参

  1. 传单个参数查询
    1. skill.xml

       <select id="get" parameterType="int" resultType="com.zh.bean.Skill">
           SELECT * FROM skill WHERE id = #{id}
       </select>
      
    2. SkillTest测试代码

       @Test
       //MyBatises是自己封装的一个工具类,对前面代码做了封装
       public void select2() throws Exception {
           try (SqlSession session = MyBatises.openSession()) {
               Skill skill = session.selectOne("skill.get", 1);
               System.out.println(skill);
           }
       }
      
    3. #{id}是参数的占位符,名字任意

  2. 传递多个参数
    1. 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>
      
    2. 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);
               }
           }
       }
      
    3. #{name}、${level}是参数的占位符,名字取决于传入参数的属性名

  3. 传参规则
    1. ${}:直接文本替换
      1. 在模糊查询的SQL中,'%${key}%'可以正常使用
    2. #{}:预编译传值,可以防止SQL注入
      1. 在模糊查询的SQL中,'%#{key}%'不能正常使用
    3. 举例
      1. skill.xml

         <select id="list3" parameterType="String" resultType="com.zh.bean.Skill">
             SELECT * FROM skill WHERE name LIKE #{name}
         </select>
        
      2. 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. 方法1:在主配置文件(mybatis-config.xml)的settings标签中增加一个配置

     <setting name="logImpl" value="STDOUT_LOGGING"/>
    
  2. 方法2:使用第三方日志输出库,比如logback,直接在pom.xml中添加依赖库即可

     <dependency>
         <groupId>ch.qos.logback</groupId>
         <artifactId>logback-classic</artifactId>
         <version>1.2.3</version>
         <scope>test</scope>
     </dependency>
    

多表关联查询

  1. 场景:
    1. 工作经验experience这个bean有个公司company字段,即该字段的类型是对象类型
    2. 如果按照上面的查询方式,查询的结果是不会直接将数据转化为company对象类型的
  2. 举例使用
    1. 项目职工新增Experience、Comany这两个bean
    2. 在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>
      
    3. 在mybatis-config.xml中添加这个映射路径

       <mapper resource="mappers/experience.xml"/>
      
    4. 新建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);
               }
           }
       }
      
      1. 结果:通过打断点发现查询出来的所有experience对象的company字段都为null,说明并没有直接转化成company对象
  3. 解决办法
    1. 方法一:通过字段映射-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>
      
    2. 方法二:对象直接映射,通过键盘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>
      
  4. 将数据映射成Map返回
    1. 修改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>
      
    2. 修改ExperienceTest类的查询代码

       List<Map<String, Object>> experiences = session.selectList("experience.list");
       for (Map<String, Object> experience : experiences) {
           System.out.println(experience);
       }
      

添加

插入一条数据

  1. 在skill.xml中新增添加语句

     <insert id="insert" parameterType="com.zh.bean.Skill">
         INSERT INTO skill(name, level) VALUES(#{name}, #{level})
     </insert>
    
  2. 在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();
         }
     }
    
  3. 注意:

    1. openSession的参数默认值是false,不自动提交事务
    2. 可以通过构造方法直接传参true,也可以主动调用session.commit();提交
    3. 如果是查询语句select则不需要提交事务

插入并返回主键id

  1. 场景:通常需要在插入完一条数据之后返回这条数据的id值
  2. 即设置新插入记录的主键(id)到参数对象中
  3. 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>
    
  4. SkillTest测试代码

     Skill skill = new Skill("Android", 999);
     session.insert("skill.insert2", skill);
     System.out.println(skill.getId());
    
  5. 还有另外一种方法:使用useGeneratedKeys、keyProperty属性
    1. 需要数据库驱动支持,比如可以适用于MySQL,不适用于Oracle
    2. skill.xml代码

       <insert id="insert3"
               useGeneratedKeys="true"
               keyProperty="id"
               parameterType="com.zh.bean.Skill">
           INSERT INTO skill(name, level) VALUES(#{name}, #{level})
       </insert>
      
    3. SkillTest测试代码一样,只是查询为insert3
    4. useGeneratedKeys:代表使用自动生成的主键
    5. keyProperty:告诉主键属性对应的bean对象的那个属性名称

更新

  1. skill.xml

     <!-- 更新语句-->
     <update id="update" parameterType="com.zh.bean.Skill">
         UPDATE skill SET name = #{name}, level = #{level} WHERE id = #{id}
     </update>
    
  2. SkillTest测试代码

     Skill skill = new Skill("Android", 666);
     skill.setId(5);
     session.update("skill.update", skill);
    

删除

  1. skill.xml

     <!-- 删除语句,传值为id即可,int类型-->
     <delete id="delete" parameterType="int">
         DELETE FROM skill WHERE id = #{id}
     </delete>
    
  2. SkillTest测试代码

     ...
     session.delete("skill.delete", 22);
     session.commit();
     ...
    
Table of Contents