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还提供了内置的类型别名:
基本类型:
别名 | 类型 | 别名 | 类型 |
---|---|---|---|
int | int | _integer | int |
_short | short | _long | long |
_double | double | _float | float |
_byte | byte | _boolean | boolean |
包装类型:
别名 | 类型 | 别名 | 类型 |
---|---|---|---|
string | String | byte | Byte |
long | Long | short | Short |
int | Integer | integer | Integer |
double | Double | float | Float |
boolean | Boolean | date | Date |
object | Object | collection | Collection |
list | List | arraylist | ArrayList |
map | Map | hashmap | HashMap |
iterator | Iterator |
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中