MyBatis学习1


  

1概述

1.1 MyBatis的jar包

  MyBatis的jar包非常轻量,MyBatis框架的解压目录中只有一个Jar包,它是MyBatis的核心Jar包。另外,还有个lib目录,其中存放着MyBatis所依赖的Jar包。所以,使用MyBatis,需要将其核心Jar包,及lib下的所有Jar包导入。
在这里插入图片描述
在这里插入图片描述

1.2 MyBatis概述

MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache迁移到了google,并更名为MyBatis。2013年迁移到Github。
MyBatis是一个优秀的基于Java的持久层框架,它内部封装了JDBC,使开发者只需关注SQL语句本身,而不用再花费精力去处理诸如注册驱动、创建Connection、配置Statement等繁杂过程。Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatement等)配置起来,并通过Java对象和Statement中SQL的动态参数进行映射生成最终执行的SQL语句,最后由MyBatis框架执行SQL并将结果映射成Java对象并返回。

1.3 MyBatis与HibernateHibernate

  框架是提供了全面的数据库封装机制的“全自动”ORM,即实现了POJO 和数据库表之间的映射,以及SQL 的自动生成和执行。相对于此,MyBatis只能算作是“半自动”ORM。其着力点,是在POJO类与SQL语句之间的映射关系。也就是说,MyBatis并不会为程序员自动生成SQL 语句。具体的SQL 需要程序员自己编写,然后通过SQL语句映射文件,将SQL所需的参数,以及返回的结果字段映射到指定POJO。因此,MyBatis成为了“全自动”ORM的一种有益补充。与Hibernate相比,MyBatis具有以下几个特点:
  (1)在XML文件中配置SQL语句,实现了SQL语句与代码的分离,给程序的维护带来了很大便利。
  (2)因为需要程序员自己去编写SQL语句,程序员可以结合数据库自身的特点灵活控制SQL语句,因此能够实现比Hibernate等全自动ORM框架更高的查询效率,能够完成复杂查询。
  (3)简单,易于学习,易于使用,上手快。

1.4 MyBatis体系结构

在这里插入图片描述

1.5 MyBatis工作原理

在这里插入图片描述

2 第一个MyBatis程序

2.1 创建MyBatis程序

1、导入Mybaits依赖

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.chengzi</groupId>
    <artifactId>mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.5</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>

</project>

2、创建实体类

public class Student {
    private Integer id;
    private String name;
    private int age;
    private double score;
    //省略setter\getter\toString方法
}

3、创建接口和实现类

public interface IStudentDao {
    void insertStu(Student studnet);
}
public class StudentDaoImpl implements IStudentDao {

    private SqlSession sqlSession;
    public void insertStu(Student student) {
        try {
            //1.加载主配置文件
            InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
            //2.创建SqlSessionFactory对象
            SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            //3.创建SqlSession对象
            sqlSession = sessionFactory.openSession();
            //4、执行插入
            sqlSession.insert("insertStudent",student);
            //5、事务提交
            sqlSession.commit();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
}

4、创建Mybatis主配置文件

<?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表示使用默认事务管理Manage表示由第三方框架管理事务,此时配置文件可以省略-->
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>

        <environment id="online">
            <!--jdbc表示使用默认事务管理Manage表示由第三方框架管理事务,此时配置文件可以省略-->
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/chengzi/dao/mapper.xml"/>
    </mappers>
</configuration>

5、创建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="test">
    <insert id="insertStudent" parameterType="com.chengzi.beans.Student">
        insert into student(name,age,score) values (#{name},#{age},#{score})
    </insert>
</mapper>

6、编写测试类

public class MyTest {
    IStudentDao iStudentDao;

    @Before
    public void before(){
        iStudentDao = new StudentDaoImpl();
    }

    @Test
    public void testInsert(){
        iStudentDao.insertStu(new Student("张三",23,93.5));
    }
}
注意:
·		1、事务必须提交才能生效
		2、sqlsession.close()方法是让事务进行回滚使用的

2.2 namespace的作用

1、首先复制mapper.xml命名为mapper2.xml
2、将mapper2.xml在mybatis主配置文件中进行注册
3、运行测试方法,会报错、提示重复的命名空间
4、改mapper2.xml的namespace为test2.xml
5、再次运行报错,模棱两可的id :insertStudent
6、解决方案两种:
  (1)调用时指定调用哪个namesapce的id
  (2)namesapce不用指定,但是两个xml中的插入方法的id不能一样

2.3 创建工具类

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    public static SqlSession getSqlSession() throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis.xml");
        if (sqlSessionFactory == null)  {
             sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        }
        return sqlSessionFactory.openSession();
    }
}
public class StudentDaoImpl implements IStudentDao {

    private SqlSession sqlSession;
    public void insertStu(Student student) {
        try {
            sqlSession = MybatisUtils.getSqlSession();
            //4、执行插入
            this.sqlSession.insert("insertStudent",student);
            //5、事务提交
            this.sqlSession.commit();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
}

3 主配置文件详解

主配置文件名可以随意命名,其主要完成以下几个功能:
  (1)注册存放DB连接四要素的属性文件
  (2)注册实体类的全限定性类名的别名
  (3)配置MyBatis运行环境,即数据源与事务管理器
  (4)注册映射文件

3.1 从属性文件中读取DB连接四要素

1、创建jdbc.poroperties文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test
jdbc.username=root
jdbc.password=root

2、修改主配置文件

<?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"></properties>
    <!--配置运行环境-->
    <environments default="development">
        <environment id="development">
            <!--jdbc表示使用默认事务管理Manage表示由第三方框架管理事务,此时配置文件可以省略-->
            <transactionManager type="JDBC"/>
            <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>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/chengzi/dao/mapper.xml"/>
        <mapper resource="com/chengzi/dao/mapper2.xml"/>
    </mappers>
</configuration>

3.2 指定实体类全限定性类名的别名

对于实体类的全限定性类名的别名指定方式,一般使用方式。这样做的好处是会将该包中所有实体类的简单类名指定为别名,写法简单方便。

<typeAliases>
        <package name="com.chengzi.beans"/>
</typeAliases>

不过,还有另外的指定方式:通过指定。type:全限定性类名alias:别名该方式的好处是,可以指定别名为简单类名以外的其它名称。当然,弊端是,必须逐个指定,比较繁琐。

<typeAliases>
        <typeAlias type="com.chengzi.beans.Student" alias="ss"/>
</typeAliases>

除了自定义的类型的别名外,MyBatis还提供了内置的类型别名:
基本类型:

别名类型别名类型
intint_integerint
_shortshort_longlong
_doubledouble_floatfloat
_bytebyte_booleanboolean

包装类型:

别名类型别名类型
stringStringbyteByte
longLongshortShort
intIntegerintegerInteger
doubleDoublefloatFloat
booleanBooleandateDate
objectObjectcollectionCollection
listListarraylistArrayList
mapMaphashmapHashMap
iteratorIterator

3.3 配置MyBatis的运行环境

配置MyBatis的运行环境,主要是配置数据源与事务管理器。
A、environments标签在
中可以包含多个运行环境,但其default属性指定了当前MyBatis运行时所选择使用的环境。environment的id属性为当前定义的运行环境的名称,可以任意命名。该名称会作为的default属性的值出现。

B、transactionManager标签
该标签用于指定MyBatis所使用的事务管理器。MyBatis支持两种事务管理器类型:JDBC与MANAGED。
  (1)JDBC:使用JDBC的事务管理机制。即,通过Connection的commit()方法提交,通过rollback()方法回滚。但默认情况下,MyBatis将自动提交功能关闭了,改为了手动提交。即程序中需要显式的对事务进行提交或回滚。从日志的输出信息中可以看到。
  (2)MANAGED:由容器来管理事务的整个生命周期(如Spring容器)

C、dataSource标签该标签用于配置MyBatis使用的数据源类型与数据库连接基本属性。常见有类型有:UNPOOLED、POOLED、JDNI等。
  (1)UNPOOLED :不使用连接池。即每次请求,都会为其创建一个DB连接,使用完毕后,会马上将此连接关闭。
  (2)POOLED:使用数据库连接池来维护连接。
  (3)JNDI:数据源可以定义到应用的外部,通过JNDI容器获取数据库连接。

3.4 指定映射文件

指定映射文件的方式有多种。但所有的方式,都是指定在mappers标签中的。

指定映射文件若映射文件只有一个,则可直接使用如下形式:

<mappers>
        <mapper resource="com/chengzi/dao/mapper.xml"/>
        <mapper resource="com/chengzi/dao/mapper2.xml"/>
</mappers>

(了解)该方式的好处是,可以将映射文件放在本地或网络的任意位置,通过其url地址即可直接访问。但通常映射文件是存放在当前应用中的,所以该方式不常用。

<mappers>
        <mapper url="file://F:\code\mybatis\src\main\java\com\chengzi\dao\mapper2.xml"/>
</mappers>

class属性值为Dao接口的全类名
该方式的使用,需要满足以下几个要求:
(1)映射文件名要与Dao接口名称相同
(2)映射文件要与接口在同一包中
(3)映射文件中的namespace属性值为Dao接口的全类名

<mappers>
        <mapper class="com.chengzi.dao.IStudentDao"/>
</mappers>

当映射文件较多时,也可以使用如下形式。其中package的name属性指定映射文件所存放的包。但,这种方式的使用需要满足以下几个条件:
(1)dao使用mapper动态代理实现(后面讲)
(2)映射文件名要与Dao接口名称相同
(3)映射文件要与接口在同一包中
(4)映射文件中的namespace属性值为Dao接口的全类名

<mappers>
        <package name="com.chengzi.dao"/>
</mappers>

4 API解析

 Dao中需要通过SqlSession对象来操作DB。而SqlSession对象的创建,需要其工厂对象SqlSessionFactory。SqlSessionFactory对象,需要通过其构建器对象SqlSessionFactoryBuilder的build()方法,在加载了主配置文件的输入流对象后创建

4.1 Resources类

 Resources类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析资源文件,返回不同类型的IO流对象。
在这里插入图片描述

4.2 SqlSessionFactoryBuilder类

 SqlSessionFactory的创建,需要使用SqlSessionFactoryBuilder对象的build()方法。由于SqlSessionFactoryBuilder对象在创建完工厂对象后,就完成了其历史使命,即可被销毁。所以,一般会将该SqlSessionFactoryBuilder对象创建为一个方法内的局部对象,方法结束,对象销毁。
 其被重载的build()方法较多
在这里插入图片描述

4.3 SqlSessionFactory接口

 SqlSessionFactory接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用只需要一个该对象即可。创建SqlSession需要使用SqlSessionFactory接口的的openSession()方法。
(1)openSession(true):创建一个有自动提交功能的SqlSession
(2)openSession(false):创建一个非自动提交功能的SqlSession,需手动提交
(3)openSession():同openSession(false)

4.4 SqlSession接口

 SqlSession接口对象用于执行持久化操作。一个SqlSession对应着一次数据库会话,一次会话以SqlSession对象的创建开始,以SqlSession对象的关闭结束。
 SqlSession接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其close()方法,将其关闭。再次需要会话,再次创建。而在关闭时会判断当前的SqlSession是否被提交:若没有被提交,则会执行回滚后关闭;若已被提交,则直接将SqlSession关闭。所以,SqlSession无需手工回滚。
 SqlSession接口常用的方法有:
在这里插入图片描述
在这里插入图片描述

5 源码分析

5.1 输入流的关闭

 在输入流对象使用完毕后,不用手工进行流的关闭。因为在输入流被使用完毕后,SqlSessionFactoryBuilder对象的build()方法会自动将输入流关闭。
在这里插入图片描述
在这里插入图片描述

5.2 SqlSession的创建

 SqlSession对象的创建,需要使用SqlSessionFactory接口对象的openSession()方法。SqlSessionFactory接口的实现类为DefaultSqlSessionFactory。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
 从以上源码可以看到,无参的openSession()方法,将事务的自动提交直接赋值为false。而所谓创建SqlSession,就是加载了主配置文件,创建了一个执行器对象(将来用于执行映射文件中的SQL语句),初始化了一个DB数据被修改的标志变量dirty,关闭了事务的自动提交功能。

5.3 增删改的执行

对于SqlSession的insert()、delete()、update()方法,其底层均是调用执行了update()方法。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  从以上源码可知,无论执行增、删还是改,均是对数据进行修改,均将dirty变量设置为了true,且在获取到映射文件中指定id的SQL语句后,由执行器executor执行。

5.4 SqlSession的提交commit()

在这里插入图片描述
在这里插入图片描述
由以上代码可知,isCommitOrRollbackRequired(force)方法的返回值为true。继承跟踪executor的commit()方法:
在这里插入图片描述
由以上代码可知,执行SqlSession的无参commit()方法,最终会将事务进行提交

5.5 SqlSession的关闭

如果正常提交:为以下分析逻辑
在这里插入图片描述
在这里插入图片描述

以上代码可知,isCommitOrRollbackRequired(force)方法的返回值为false。继承跟踪executor的close()方法:
在这里插入图片描述
在这里插入图片描述
相反的如果不执行提交,dirty是true,isCommitOrRollbackRequired(force)运算完的结果是true,事务就会进行回滚。

6 单表的CRUD

6.1 插入数据

6.1.1 单纯插入数据

1、修改Dao接口

public interface IStudentDao {

    //插入
    void insertStu(Student studnet);

    void insertStudentCachId(Student student);

    //删除
    void deleteStudentById(int id);

    //改
    void updateStudent(Student student);

    //查询所有
    List<Student> selectAllStudents();
    Map<String,Student> selectStudentMap();

    //查询指定学生
    Student selectStudentById(int id);
    Student selectStudentByMap(Map<String,Object> map);

    //根据姓名查询
    Student selectStudentByName(String name);
}

2、修改Dao实现类
Dao实现类除了已经实现的insertStudent()方法外,其它方法暂时先以空方法体的方式实现

public class StudentDaoImpl implements IStudentDao {

    private SqlSession sqlSession;
    public void insertStu(Student student) {
        try {
            sqlSession = MybatisUtils.getSqlSession();
            //4、执行插入
            this.sqlSession.insert("insertStudent",student);
            //5、事务提交
            this.sqlSession.commit();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
    //省略
}    

 需要注意的是,执行完对DB的修改操作,必须要做SqlSession的提交。否则,修改将无法同步到DB中。因为使用无参的openSession()方法已经将事务的自动提交功能给关闭了。

3、测试类
 对CURD的测试,使用JUnit进行测试。每个方法在测试前,首先需要创建和获取Dao对象。所以,将Dao的创建放在了@Before注解的方法中。

public class MyTest {
    IStudentDao iStudentDao;

    @Before
    public void before(){
        iStudentDao = new StudentDaoImpl();
    }

    @Test
    public void testInsert(){
        iStudentDao.insertStu(new Student("张三1",23,93.5));
    }
}

4、插入数据

<?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="test">
    <insert id="insertStudent" parameterType="com.chengzi.beans.Student">
        insert into student(name,age,score) values (#{name},#{age},#{score})
    </insert>
</mapper>

(1)id:该SQL语句的唯一标识,Java代码中要使用该标识
(2)#{ }:对指定参数类型属性值的引用。其底层是通过反射机制,调用Student类相关属性的get方法来获取值的。因为底层使用的是反射,所以这里使用的是类的属性名,而非表的字段名。
5、构建测试数据

 @Test
public void testInsert(){
     //iStudentDao.insertStu(new Student("张三1",23,93.5));
     for (int i = 1;i <= 10; i++) {
          Student student = new Student("n_" + i,15+i, 85.5+i);
          iStudentDao.insertStu(student);
      }
}

6.1.2 插入后用新id初始化被插入对象

MySql中在插入语句后执行如下语句,则会输出新插入记录的id:

INSERT INTO student(name,age,score) VALUES ('赵六',25,15.5);
SELECT @@IDENTITY;
SELECT LAST_INSERT_ID();

 映射文件的insert标签中,有一个子标签selectKey用于获取新插入记录的主键值。以下两种写法均可以完成“使用新插入记录的主键值初始化被插入的对象”的功能
1、修改xml

<insert id="insertStudentCachId" parameterType="com.chengzi.beans.Student">
        insert into student(name,age,score) values (#{name},#{age},#{score})
        <selectKey resultType="int" keyProperty="id" order="AFTER">
            SELECT  @@IDENTITY
        </selectKey>
</insert>

或者

<insert id="insertStudentCachId" parameterType="com.chengzi.beans.Student">
        insert into student(name,age,score) values (#{name},#{age},#{score})
        <selectKey resultType="int" keyProperty="id" order="AFTER">
            SELECT LAST_INSERT_ID()
        </selectKey>
</insert>

 (1)resultType:指出获取的主键的类型。
 (2)keyProperty:指出主键在Java类中对应的属性名。此处会将获取的主键值直接封装到被插入的Student对象中,即dao中insert()方法的第二个参数对象中。
 (3)order:指出id的生成相对于insert语句的执行是在前还是在后。MySql数据库表中的id,均是先执行insert语句,而后生成id,所以需要设置为AFTER;Oracle数据库表中的id,则是在insert执行之前先生成,所以需要设置为BEFORE。当前的MyBatis版本,不指定order属性,则会根据所用DBMS,自动选择其值

2、修改实现类

 @Override
    public void insertStudentCachId(Student student) {
        try {
            sqlSession = MybatisUtils.getSqlSession();
            this.sqlSession.insert("insertStudentCachId",student);
            this.sqlSession.commit();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }

3、测试类

	@Test
    public void testInsertCathchId(){
        Student student = new Student("cacheId",17,99);
        System.out.println("插入前->" + student.getId());
        iStudentDao.insertStudentCachId(student);
        System.out.println("插入后->" + student.getId());
    }
插入前->null
log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
[log4j-demo] 2019-09-14 14:02:44,077 [DEBUG] test.insertStudentCachId:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - ==>  Preparing: insert into student(name,age,score) values (?,?,?) 
[log4j-demo] 2019-09-14 14:02:44,095 [DEBUG] test.insertStudentCachId:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - ==> Parameters: cacheId(String), 17(Integer), 99.0(Double)
[log4j-demo] 2019-09-14 14:02:44,102 [DEBUG] test.insertStudentCachId:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - <==    Updates: 1
[log4j-demo] 2019-09-14 14:02:44,102 [DEBUG] test.insertStudentCachId!selectKey:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - ==>  Preparing: SELECT @@IDENTITY 
[log4j-demo] 2019-09-14 14:02:44,103 [DEBUG] test.insertStudentCachId!selectKey:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - ==> Parameters: 
[log4j-demo] 2019-09-14 14:02:44,116 [DEBUG] test.insertStudentCachId!selectKey:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - <==      Total: 1
插入后->28

Process finished with exit code 0

4、分析id是何时获取到的?
 这个新插入数据的id是何时获取到的呢?是在插入操作完成后由DB回传给Dao的实现类的吗?在Dao的实现类中insert()方法后,commit()方法前插入一条输出student对象的语句,并在commit()方法处添加一个断点。
在这里插入图片描述
 以调试方式运行,发现在插入操作还未提交时student对象已经有了id。这说明一个问题:无论插入操作是提交还是回滚,DB均会为insert的记录分配id,即使发生回滚,这个id也已经被使用。后面再插入并提交的记录数据,此id已经不能再使用,被分配的id是跳过此id后的id。另外,从前面selectKey中order属性值的设置可知,MySql在insert语句执行后会自动生成该新插入记录的主键值。主键值的生成只与insert语句是否执行有关,而与最终是否提交无关。

注意:
insert 标签中的parameterType 可以省略

6.2 删除数据

1、修改xml

<delete id="deleteStudentById">
        delete from student where id = #{xxx}
</delete>
注意:
这里的动态参数id所赋值为#{xxx}。这个#{xxx}表示这就是个占位符,代表delete()方法的第二个参数。
#{ }中可以放任意值,无需要与delete()方法的第二个参数值相同。

2、实现类

	@Override
    public void deleteStudentById(int id) {
        try {
            sqlSession = MybatisUtils.getSqlSession();
            this.sqlSession.insert("deleteStudentById",1);
            this.sqlSession.commit();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }

3、测试类

@Test
    public void testDeleteStudentById(){
        iStudentDao.deleteStudentById(1);
    }

6.3 修改数据

1、修改xml

<delete id="updateStudent">
        update student set name = #{name},age=#{age},score=#{score} where id = #{id}
</delete>
注意:
这里的#{ }中,必须要填写update()方法所传第二个参数student对象的属性名称,不能随意填写。

2、修改实现类

	@Override
    public void updateStudent(Student student) {
        try {
            sqlSession = MybatisUtils.getSqlSession();
            this.sqlSession.insert("updateStudent",student);
            this.sqlSession.commit();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }

3、修改测试类

	 @Test
    public void testUpdateStudent(){
        Student student = new Student("updated",17,99);
        student.setId(2);
        iStudentDao.updateStudent(student);
    }

6.4 查询

6.4.1 查询所有对象-返回List

1、修改映射文件

<select id="selectAllStudents" resultType="com.chengzi.beans.Student">
        select * from student
</select>

 resultType属性并非指查询结果集最后的类型,而是每查出DB中的一条记录,将该记录封装成为的对象的类型。这里resultType属性使用的是全限定性类名。对于一个映射文件来说,一般情况下是对一个类的操作均放在同一个映射文件。所以,一个映射文件中出现的类一般是相同的。而每一个需要指定类名的地方若均需指定全限定性类名,会比较麻烦。所以,MyBatis支持为类起别名的方式。

2、修改dao的实现类

	@Override
    public List<Student> selectAllStudents() {
        List<Student> list = null;
        try {
            sqlSession = MybatisUtils.getSqlSession();
            list = this.sqlSession.selectList("selectAllStudents");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
        return list;
    }

 完成Dao实现类的selectAllStudent()方法。使用SqlSession的selectList()方法完成查询操作。该方法会将查询出的每条记录封装为指定类型对象后,再将最后的结果集封装为List返回。方法原型为:List selectList (Stringstatement)。statement:映射文件中配置的SQL语句的id。
 在写查询时,由于不是对DB中数据进行修改,所以无需进行SqlSession的提交。但最终SqlSession对象还是需要关闭的。

3、修改测试类

	@Test
    public void testSelectAllStudents() {
        List<Student> students = iStudentDao.selectAllStudents();
        students.forEach(s -> System.out.println(s));

    }

6.4.2 查询所有对象-返回Map

1、映射文件不用修改
2、修改Dao的实现类
 完成Dao实现类的selectStudentMap()方法。使用SqlSession的selectMap()方法完成查询操作。该查询方法会将查询出的每条记录先封装为指定对象,然后再将该对象作为value,将该对象的指定属性所对应的字段名作为key封装为一个Map对象。
 方法原型为:Map<Object,Object>selectMap(Stringstatement, String mapKey)。statement:映射文件中配置的SQL语句的id。mapKey:查询出的Map所要使用的key。这个key为数据表的字段名。查询出的结果是一个Map,每行记录将会对应一个Map.entry对象,该对象的key为指定字段的值,value为记录数据所封装的对象

	@Override
    public Map<String, Student> selectStudentMap() {
        Map<String,Student> map = null;
        try {
            sqlSession = MybatisUtils.getSqlSession();
            map = this.sqlSession.selectMap("selectAllStudents","name");
            this.sqlSession.commit();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
        return map;
    }

3、修改测试类

	@Test
    public void testselectStudentMap() {
        Map<String, Student> map = iStudentDao.selectStudentMap();
        map.forEach((k , v) -> System.out.println(k + "->" + v));
    }
注意:
若指定的作为key的属性值在DB中并不唯一,则后面的记录值会覆盖掉前面的值。即指定key的value值,
一定是DB中该同名属性的最后一条记录值。

6.4.3 查询单个对象

1、修改xml

<select id="selectStudentById" resultType="com.chengzi.beans.Student">
        select * from student where id = #{**}
</select>

2、实现类

 使用SqlSession的selectOne()方法。其会将查询的结果记录封装为一个指定类型的对象。方法原型为:Object selectOne (Stringstatement,Objectparameter)statement:映射文件中配置的SQL语句的idparameter:查询条件中动态参数的值

	@Override
    public Student selectStudentById(int id) {

        Student student = null;
        try {
            sqlSession = MybatisUtils.getSqlSession();
            student = this.sqlSession.selectOne("selectStudentById",id);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
        return student;
    }

3、修改测试类

	 @Test
    public void testSelectStudentById(){
        Student student = iStudentDao.selectStudentById(3);
        System.out.println(student);
    }
注意:
parameterType参数在 insert、delete、update、select中都可省略
resulType在select中不可省略

6.4.4 模糊查询

1、修改映射文件

<select id="selectStudentByName" resultType="com.chengzi.beans.Student">
        select * from student where name like concat('%',#{ooo},'%')
</select>
<select id="selectStudentByName" resultType="com.chengzi.beans.Student">
        select * from student where name like '%'#{ooo}'%'
</select>

以上两种形式是等效的。都是以动态参数的形式出现在SQL语句中的。

[log4j-demo] 2019-09-14 15:47:01,997 [DEBUG] test.selectStudentByName:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - ==>  Preparing: select * from student where name like '%'?'%' 
[log4j-demo] 2019-09-14 15:47:02,021 [DEBUG] test.selectStudentByName:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - ==> Parameters: n(String)
[log4j-demo] 2019-09-14 15:47:02,035 [DEBUG] test.selectStudentByName:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - <==      Total: 0

还可使用如下方式,只是需要注意,只能能使用${}中只能使用value,不能使用其它。
这种方式是纯粹的字符串拼接,直接将参数拼接到了SQL语句中。这种方式可能会发生SQL注入。

<select id="selectStudentByName" resultType="com.chengzi.beans.Student">
        select * from student where name like '%${value}%'
</select>
[log4j-demo] 2019-09-14 15:45:53,233 [DEBUG] test.selectStudentByName:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - ==>  Preparing: select * from student where name like '%n%' 
[log4j-demo] 2019-09-14 15:45:53,262 [DEBUG] test.selectStudentByName:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - ==> Parameters: 
[log4j-demo] 2019-09-14 15:45:53,280 [DEBUG] test.selectStudentByName:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - <==      Total: 10

2、修改dao的实现类

	@Override
    public List<Student> selectStudentByName(String name) {
        List<Student> list = null;
        try {
            sqlSession = MybatisUtils.getSqlSession();
            list = this.sqlSession.selectList("selectStudentByName",name);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
        return list;
    }

3、修改测试类

	@Test
    public void testSelectStudentByName(){
        List<Student> list = iStudentDao.selectStudentByName("n");
        list.forEach(s -> System.out.println(s));
    }

4、$与#的区别

A、理论区别KaTeX parse error: Expected 'EOF', got '#' at position 2: 与#̲的区别是很大的。#为占位符,而为字符串拼接符。字符串拼接是将参数值以硬编码的方式直接拼接到了SQL语句中。字符串拼接就会引发两个问题:SQL注入问题与没有使用预编译所导致的执行效率低下问题。占位符的引入,解决以上两个问题
B、一般情况下,动态参数的值是由用户输入的,则不能使用拼接符 , 因 为 有 可 能 会 出 现 S Q L 注 入 ; 若 动 态 参 数 的 值 是 由 系 统 计 算 生 成 的 , 则 可 以 使 用 拼 接 符 ,因为有可能会出现SQL注入;若动态参数的值是由系统计算生成的,则可以使用拼接符 SQL使。但这样虽然不存在SQL注入的风险,但仍存在执行效率问题。

5、SQL注入问题
 例如,有一个用于根据姓名进行查询的表单
在这里插入图片描述
其后台SQL语句为拼接字符串:

String sql =select * from student where name like%+ name +%’”; 

若用户正常输入,则查出所有“张”姓学生是没有问题的。执行的sql语句为:

select * from student where name like%%

但,若用户有意进行SQL注入,则会输入如下:
在这里插入图片描述
此时表单获取的表单属性值,即name值为:张%‘ or 1=1 or ’ 。与后台的sql语句拼接一块,则形成如下的sql:

select * from student where name like '%张%' or 1=1 or '%'

 此SQL的执行结果为,查询出所有数据记录。但是,若在进行查询时,使用的为PreparedStatement,而非Statement,则可防止SQL注入的产生。
 在MyBatis中,使用#号占位符,则后台执行SQL使用的为PreparedStatement,将会防止SQL注入。而使用$符,则为字符串拼接,使用的为Statement,将无法防止SQL注入。

6.4.5 SQL的预编译问题

当Java代码通过JDBC的Statement向DB发送一条SQL语句时,DBMS会对SQL语句编译后执行。
在这里插入图片描述
 当Java代码通过JDBC的PreparedStatement向DB发送一条SQL语句时,DBMS会首先编译SQL语句,然后将编译好的SQL放入到DBMS的数据库缓存池中再执行。当DBMS再次接收到对该数据库操作的SQL时,先从DB缓存池中查找该语句是否被编译过,若被编译过,则直接执行,否则先编译后将编译结果放入DB缓存池,再执行。
在这里插入图片描述

6.4.6 根据Map进行查询

mapper中SQL语句的动态参数也可以是Map的key。
1、修改映射文件

<select id="selectStudentByMap" resultType="com.chengzi.beans.Student">
        select * from student where id between #{studentId} and #{stu.id}
</select>

2、修改Dao实现类

 	@Override
    public List<Student> selectStudentByMap(Map<String, Object> map) {
        List<Student> list = null;
        try {
            sqlSession = MybatisUtils.getSqlSession();
            list = this.sqlSession.selectList("selectStudentByMap", map);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
        return list;
    }

3、修改测试类

	 @Test
    public void testSelectStudentByMap() {
        Student student = new Student();
        student.setId(5);
        Map<String, Object> map = new HashMap<>();
        map.put("studentId",2);
        map.put("stu",student);
        List<Student> students = iStudentDao.selectStudentByMap(map);
        students.forEach(s -> System.out.println(s));
    }

7 属性名与查询字段名不一致

 rsultType可以将查询结果直接映射为实体Bean对象的条件是,SQL查询的字段名与实体Bean的属性名一致。因为在将查询结果转换为指定类型对象时,系统自动将查询结果字段名称作为对象的属性名,通过反射机制完成对象的创建。
 当SQL查询结果的字段名和实体Bean的属性名不一致时,将无法创建出需要类型的对象。此时有两种解决方案。
修改表
DB表的score字段名称与Student类的score属性同名,其它字段名与属性名均不相同。
在这里插入图片描述

7.1 使用别名

 虽然属性名称与表中字段名称不一致,但可以为查询结果的字段名称赋予别名,让别名与实体Bean的属性名相同。这样框架也可以根据查询结果利用反射机制将对象创建。
 在映射文件mapper中添加如下映射。注意,由于表的score字段名与Student类的属性名同名,所以这里无需使用别名。
1、修改xml

<select id="selectStudentById2" resultType="com.chengzi.beans.Student">
        select 
            t_id as id
            t_name as name 
            t_age as age
            score 
        from student where t_id = #{**}
</select>

2、实现类

	@Override
    public Student selectStudentById2(int id) {
        Student student = null;
        try {
            sqlSession = MybatisUtils.getSqlSession();
            student = this.sqlSession.selectOne("selectStudentById2",id);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
        return student;
	}

3、测试类

	@Test
    public void testSelectStudentById2(){
        Student student = iStudentDao.selectStudentById2(3);
        System.out.println(student);
    }

7.2 使用结果映射resultMap

 可以使用结果映射resultMap(这里的Map是映射mapper的意思)来建立映射关系,完成由字段到属性的映射,达到将查询结果封装为对象的目的。resultMap是对resultType的增强。

<resultMap id="studenteMapper" type="com.chengzi.beans.Student">
        <id property="id" column="t_id"/>
        <id property="name" column="t_name"/>
        <id property="age" column="t_age"/>
</resultMap>
<select id="selectStudentById2" resultMap="studenteMapper">
        select
            t_id,t_name,t_age,score
        from student where t_id = #{**}
</select>

  result Map标签中定义了由type指定的类的属性名到表中字段名称的映射关系。根据这个映射关系,框架利用反射机制创建相应对象。
 (1)type:指定要映射的实体类
 (2)id:指定该resultMap映射关系的名称
 (3)id标签:id的字段名column与实体类的属性property间的映射关系
 (4)result标签:id以外其它字段名column与实体类的属性property间的映射关系当然,对于字段名与实体类的属性名相同的情况,可以不写入result Map中

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值