1.MyBatis执行SQL的两种方式:SqlSession和Mapper接口
1.用 Mapper 接口发送 SQL
PersonMapper personMapper=sqlSession.getMapper(PersonMapper.class);
personMapper.insertPerson(person);
通过 SqlSession 的 getMapper 方法来获取一个 Mapper 接口,就可以调用它的方法了。因为 SQL映射 文件或者接口注解定义的 SQL 都可以通过“类的全限定名+方法名”查找,所以 MyBatis 会启用对应的 SQL 进行运行,并返回结果。
实例:
package com.wangxing.mybatis.test;
import com.wangxing.mybatis.bean.Person;
import com.wangxing.mybatis.mapper.PersonMapper;
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.util.List;
public class TestMain {
public static void testInsertPerson(){
//定义SqlSession对象
SqlSession sqlSession=null;
try {
//通过SqlSessionFactoryBuilder类创建出SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
//从SqlSessionFactory中取得一个SqlSession对象
sqlSession=sqlSessionFactory.openSession();
//通过得到数据访问接口对象调用insertPerson方法
PersonMapper personMapper=sqlSession.getMapper(PersonMapper.class);
Person person=new Person();
person.setPername("zhangsan");
person.setPerage(23);
person.setPeraddress("西安");
personMapper.insertPerson(person);
//提交sqlsession
sqlSession.commit();
}catch (Exception e){
e.printStackTrace();
sqlSession.rollback();
}finally{
sqlSession.close();
}
}
public static void testUpdatePerson(){
//定义SqlSession对象
SqlSession sqlSession=null;
try {
//通过SqlSessionFactoryBuilder类创建出SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
//从SqlSessionFactory中取得一个SqlSession对象
sqlSession=sqlSessionFactory.openSession();
//通过得到数据访问接口对象调用insertPerson方法
PersonMapper personMapper=sqlSession.getMapper(PersonMapper.class);
Person person=new Person();
person.setPerid(2);
person.setPername("lisi");
person.setPerage(24);
person.setPeraddress("beijing");
personMapper.updatePerson(person);
//提交sqlsession
sqlSession.commit();
}catch (Exception e){
e.printStackTrace();
sqlSession.rollback();
}finally{
sqlSession.close();
}
}
public static void testSelectPersonById(){
//定义SqlSession对象
SqlSession sqlSession=null;
try {
//通过SqlSessionFactoryBuilder类创建出SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
//从SqlSessionFactory中取得一个SqlSession对象
sqlSession=sqlSessionFactory.openSession();
//通过得到数据访问接口对象调用insertPerson方法
PersonMapper personMapper=sqlSession.getMapper(PersonMapper.class);
Person person=personMapper.selectPersonById(3);
//提交sqlsession
sqlSession.commit();
System.out.println(person);
System.out.println(person.getPerid()+"\t"+person.getPername());
}catch (Exception e){
e.printStackTrace();
sqlSession.rollback();
}finally{
sqlSession.close();
}
}
public static void testSelectPerson(){
//定义SqlSession对象
SqlSession sqlSession=null;
try {
//通过SqlSessionFactoryBuilder类创建出SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
//从SqlSessionFactory中取得一个SqlSession对象
sqlSession=sqlSessionFactory.openSession();
//通过得到数据访问接口对象调用insertPerson方法
PersonMapper personMapper=sqlSession.getMapper(PersonMapper.class);
List<Person> personlist=personMapper.selectPerson();
//提交sqlsession
sqlSession.commit();
System.out.println("personlist.size----"+personlist.size());
System.out.println("person----"+personlist.get(0).getPername());
}catch (Exception e){
e.printStackTrace();
sqlSession.rollback();
}finally{
sqlSession.close();
}
}
public static void testDeletePerson(){
//定义SqlSession对象
SqlSession sqlSession=null;
try {
//通过SqlSessionFactoryBuilder类创建出SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
//从SqlSessionFactory中取得一个SqlSession对象
sqlSession=sqlSessionFactory.openSession();
//通过得到数据访问接口对象调用insertPerson方法
PersonMapper personMapper=sqlSession.getMapper(PersonMapper.class);
personMapper.deletePersonById(2);
//提交sqlsession
sqlSession.commit();
}catch (Exception e){
e.printStackTrace();
sqlSession.rollback();
}finally{
sqlSession.close();
}
}
public static void main(String[] args) {
//测试添加用户信息
//testInsertPerson();
//测试修改用户信息
//testUpdatePerson();
//测试根据id查询用户信息
//testSelectPersonById();
//测试查询所有用户信息
testSelectPerson();
//测试根据id删除用户信息
//testDeletePerson();
}
}
2.SqlSession 发送 SQL
通过SqlSession对象的
int insert(“数据访问接口的包名+接口名+方法名” , 数据访问接口的方法的参数);
int update(“数据访问接口的包名+接口名+方法名” , 数据访问接口的方法的参数);
int delete(“数据访问接口的包名+接口名+方法名” , 数据访问接口的方法的参数);
selectOne(“数据访问接口的包名+接口名+方法名” , 数据访问接口的方法的参数);
List selectList(“数据访问接口的包名+接口名+方法名” , 数据访问接口的方法的参数);
…
例如:
package com.wangxing.mybatis.test;
import com.wangxing.mybatis.bean.Person;
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.util.List;
public class Test {
public static void testInsertPerson(){
SqlSession sqlSession=null;
try{
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
sqlSession=sqlSessionFactory.openSession();
Person person=new Person();
person.setPername("zhangsan");
person.setPerage(88);
person.setPeraddress("西安");
sqlSession.insert("com.wangxing.mybatis.mapper.PersonMapper.insertPerson",person);
sqlSession.commit();
}catch(Exception e){
sqlSession.rollback();
}finally {
sqlSession.close();
}
}
public static void testUpdatePerson(){
SqlSession sqlSession=null;
try{
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
sqlSession=sqlSessionFactory.openSession();
Person person=new Person();
person.setPerid(1);
person.setPername("lisi");
person.setPerage(99);
person.setPeraddress("北京");
sqlSession.update("com.wangxing.mybatis.mapper.PersonMapper.updatePerson",person);
sqlSession.commit();
}catch(Exception e){
sqlSession.rollback();
}finally {
sqlSession.close();
}
}
public static void testSelectPersonById(){
SqlSession sqlSession=null;
try{
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
sqlSession=sqlSessionFactory.openSession();
Person person=
(Person)sqlSession.selectOne("com.wangxing.mybatis.mapper.PersonMapper.selectPersonById",1);
sqlSession.commit();
System.out.println(person.getPerid()+"\t"+person.getPername());
}catch(Exception e){
sqlSession.rollback();
}finally {
sqlSession.close();
}
}
public static void testSelectPerson(){
SqlSession sqlSession=null;
try{
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
sqlSession=sqlSessionFactory.openSession();
List<Person> personList=
sqlSession.selectList("com.wangxing.mybatis.mapper.PersonMapper.selectPerson");
sqlSession.commit();
System.out.println("perlist.size=="+personList.size());
Person person=personList.get(0);
System.out.println(person.getPerid()+"\t"+person.getPername());
}catch(Exception e){
sqlSession.rollback();
}finally {
sqlSession.close();
}
}
public static void testDeletePerson(){
SqlSession sqlSession=null;
try{
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
sqlSession=sqlSessionFactory.openSession();
sqlSession.delete("com.wangxing.mybatis.mapper.PersonMapper.deletePersonByID",1);
sqlSession.commit();
}catch(Exception e){
sqlSession.rollback();
}finally {
sqlSession.close();
}
}
public static void main(String[] args) {
//测试添加数据
//testInsertPerson();
//测试修改数据
//testUpdatePerson();
//测试根据id查询
//testSelectPersonById();
//测试查询所有
//testSelectPerson();
//根据id删除信息
testDeletePerson();
}
}
3.对比两种发送 SQL 方式
上面分别展示了 MyBatis 存在的两种发送 SQL 的方式,一种用 SqlSession 直接发送,另外一种通过 SqlSession 获取 Mapper 接口再发送。建议采用 SqlSession 获取 Mapper 的方式,理由如下:
1.使用 Mapper 接口编程可以消除 SqlSession 带来的功能性代码,提高可读性,而 SqlSession 发送 SQL,需要一个 SQL id 去匹配 SQL,比较晦涩难懂。
2.使用Mapper.selectPersonById(1)方式,IDE会提示错误和校验,而使用 sqlSession.selectOne(“com.wangxing.mybatis.mapper.PersonMapper.selectPersonById”,1)语法,只有在运行中才能知道是否会产生错误。
目前使用Mapper接口编程已成为主流,尤其在Spring 中运用MyBatis 时,Mapper 接口的使用就更为简单,所以本教程使用Mapper 接口发送SQL语句并执行的方式。
2.MyBatis 的工作原理
下面对图中的每步流程进行说明。
1)读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
2)加载SQL映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。
注意:Executor 执行器执行SQL语句时,是根据SQL映射文件中对应元素的的id属性值去选择对应的sql语句,由于SQL映射文件中对应元素的的id属性值是数据访问接口的方法名,所以数据访问接口中的方法是不能重载的。
3.MyBatis的核心组件:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession和SQL Mapper
1)SqlSessionFactoryBuilder(构造器):它会根据MyBati核心配置或者代码来生成 SqlSessionFactory,采用的是分步构建的 Builder 模式。
public SqlSessionFactory getSqlSessionFactory (){
// 数据库连接池信息
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword ("123456");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDefeultAutoCommit(false);
// 采用 MyBatis 的 JDBC 事务方式
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment ("development", transactionFactory, dataSource);
// 创建 Configuration 对象
Configuration configuration = new Configuration(environment);
// 加入一个映射器
configuration.addMapper(RoleMapper.class);
//使用 SqlSessionFactoryBuilder 构建 SqlSessionFactory
SqlSessionFactory SqlSessionFactory =
new SqlSessionFactoryBuilder().build(configuration);
return SqlSessionFactory;
}
注意代码中的注释,它和 XML 方式实现的功能是一致的,只是方式不太一样而已。但是代码冗长,如果发生系统修改,那么有可能需要重新编译代码才能继续,所以这不是一个很好的方式,一般不推荐大家使用。除非有特殊的需要,比如在配置文件中,需要配置加密过的数据库用户名和密码,需要我们在生成 SqlSessionFactory 前解密为明文的时候,才会考虑使用这样的方式。
2)SqlSessionFactory(工厂接口):依靠它来生成 SqlSession,使用的是工厂模式。
SqlSession sqlSession=SqlSessionFactory对象.openSession();
3)SqlSession(会话):一个既可以发送 SQL 执行返回结果,也可以获取 Mapper 的接口。在现有的技术中,一般我们会让其在业务逻辑代码中“消失”,而使用的是 MyBatis 提供的 SQL Mapper 接口编程技术,它能提高代码的可读性和可维护性。
在 MyBatis 中有两个实现类,DefaultSqlSession 和 SqlSessionManager。
DefaultSqlSession 是单线程使用的,而 SqlSessionManager 在多线程环境下使用。
发送 SQL 执行返回结果
int insert(“数据访问接口的包名+接口名+方法名” , 数据访问接口的方法的参数);
int update(“数据访问接口的包名+接口名+方法名” , 数据访问接口的方法的参数);
int delete(“数据访问接口的包名+接口名+方法名” , 数据访问接口的方法的参数);
selectOne(“数据访问接口的包名+接口名+方法名” , 数据访问接口的方法的参数);
List selectList(“数据访问接口的包名+接口名+方法名” , 数据访问接口的方法的参数);
…
获取 Mapper 的接口
数据访问接口对象=SqlSession对象.getMapper(数据访问接口的反射对象);
4)SQL Mapper(映射器):MyBatis 新设计存在的组件,它由一个 Java 接口和 XML 文件(或注解)构成,需要给出对应的 SQL 和映射规则。它负责发送 SQL 去执行,并返回结果。
SQL Mapper(映射器)=数据访问接口+SQL映射文件/注解,负责发送 SQL 去执行,并返回结果。
4.1.数据访问接口+SQL映射文件
数据访问接口
package com.wangxing.mybatis.mapper;
import com.wangxing.mybatis.bean.Person;
import java.util.List;
public interface PersonMapper {
/**
* 添加数据
* @param person
* @return
*/
boolean insertPerson(Person person);
/**
* 修改数据
* @param person
* @return
*/
boolean updatePerson(Person person);
/**
* 删除数据
* @return
*/
boolean deletePersonById(int perid);
/**
* 根据id查询数据
* @return
*/
Person selectPersonById(int perid);
/**
* 查询所有数据
* @return
*/
List<Person> selectPerson();
}
SQL映射文件
<?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.wangxing.mybatis.mapper.PersonMapper">
<insert id="insertPerson" parameterType="com.wangxing.mybatis.bean.Person">
insert into t_person values (null,#{pername},#{perage},#{peraddress});
</insert>
<update id="updatePerson" parameterType="com.wangxing.mybatis.bean.Person">
update t_person set per_name=#{pername},per_age=#{perage},per_address=#{peraddress} where per_id=#{perid};
</update>
<resultMap id="personMap" type="com.wangxing.mybatis.bean.Person">
<id column="per_id" property="perid"></id>
<result column="per_name" property="pername"></result>
<result column="per_age" property="perage"></result>
<result column="per_address" property="peraddress"></result>
</resultMap>
<select id="selectPersonById" parameterType="int" resultMap="personMap">
select * from t_person where per_id=#{perid};
</select>
<select id="selectPerson" resultMap="personMap">
select * from t_person;
</select>
<delete id="deletePersonById" parameterType="java.lang.Integer">
delete from t_person where per_id=#{perid};
</delete>
</mapper>
4.2.数据访问接口+注解
PersonMapper.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.wangxing.mybatis.mapper.PersonMapper">
<resultMap id="perMap" type="com.wangxing.mybatis.bean.Person">
<id column="per_id" property="perid"></id>
<result column="per_name" property="pername"></result>
<result column="per_age" property="perage"></result>
<result column="per_address" property="peraddress"></result>
</resultMap>
</mapper>
数据访问接口带注解
package com.wangxing.mybatis.mapper;
import com.wangxing.mybatis.bean.Person;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface PersonMapper {
/**
* 添加数据
* @param person
* @return
*/
@Insert("insert into t_person values (null,#{pername},#{perage},#{peraddress});")
boolean insertPerson(Person person);
/**
* 修改数据
* @param person
* @return
*/
@Update("update t_person set per_name=#{pername},per_age=#{perage},per_address=#{peraddress} where per_id=#{perid};")
boolean updatePerson(Person person);
/**
* 删除数据
* @return
*/
@Delete("delete from t_person where per_id=#{perid};")
boolean deletePersonById(int perid);
/**
* 根据id查询数据
* @return
*/
@Results(id = "personMap" , value = {
@Result(column = "per_id",property = "perid"),
@Result(column = "per_name",property = "pername"),
@Result(column = "per_age",property = "perage"),
@Result(column = "per_address",property = "peraddress"),
})
@Select("select * from t_person where per_id=#{perid};")
Person selectPersonById(int perid);
/**
* 查询所有数据
* @return
*/
@Select("select * from t_person;")
@ResultMap("perMap")
List<Person> selectPerson();
}
注意:
org.apache.ibatis.binding.BindingException: Type interface com.wangxing.mybatis.mapper.PersonMapper is not known to the MapperRegistry.
因为没有在MyBatis的核心配置文件中注册数据访问接口,
解决方法:在MyBatis的核心配置文件中注册数据访问接口
MyBatis 官方推荐使用的是 sql映射文件的方式配置sql语句,因为在工作中,SQL 的复杂度远远超过我们现在看到的 SQL,比如下面这条 SQL。
select * from t_user u
left join t_user_role ur on u.id = ur.user_id
left join t_role r on ur.role_id = r.id
left join t_user_info ui on u.id = ui.user_id
left join t_female_health fh on u.id = fh.user_id
left join t_male_health mh on u.id = mh.user_id
where u.user_name like concat('%', ${userName},'%')
and r.role_name like concat('%', ${roleName},'%')
and u.sex = 1
and ui.head_image is not null;
显然这条 SQL 比较复杂,如果放入 @Select 中会明显增加注解的内容。如果把大量的SQL 放入 java 代码中,显然代码的可读性也会下降。如果同时还要考虑使用动态 SQL,比如当参数 userName 为空,则不使用 u.user_name like concat(’%’,
u
s
e
r
N
a
m
e
,
′
{userName},'%')作为查询条件;当 roleName 为空,则不使用 r.role_name like concat('%',
userName,′{roleName},’%’)作为查询条件,但是还需要加入其他的逻辑,这样就使得这个注解更加复杂了,不利于日后的维护和修改。
用一张图来展示 MyBatis 核心组件之间的关系。