MyBatis
MyBatis
主流框架 SSH (Struts2+Spring+Hibernate)
SSM(Spring MVC+Spring+MyBatis)
1.MyBatis的简介
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。实际上MyBatis对ibatis进行一些改进。2013年11月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)。
MyBatis是一个优秀的持久化层框架,它对JDBC操作数据库的过程进行了封装,使开发者只需要关注SQL本身,而不需要花费精力去处理例如注册驱动,创建Connection、创建Statement、手动设置参数、结果检索等JDBC繁杂的过程代码。
MyBatis通过XML或注解的方式将要执行的各种Statement(Statement、PreparedStatement、CallableStatement)配置起来,并通过Java对象和Statement中的SQL进行映射生成最终执行的SQL语句,最终由MyBatis框架执行SQL并将结果映射为Java对象并返回。
注:传统JDBC(贾琏欲执事 加载驱动、建立连接、sql语句,执行,释放资源)操作的过程。
2.分析原生态的JDBC程序中存在的问题
2.1 原生态的JDBC程序代码
public static void main(String[] args) { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { //1、加载数据库驱动 Class.forName("com.mysql.jdbc.Driver"); //2、通过驱动管理类获取数据库链接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_easybuy?characterEncoding=utf-8", "root", "root"); //3、定义sql语句 ?表示占位符 String sql = "select * from user where username = ?"; //4、获取预处理statement preparedStatement = connection.prepareStatement(sql); //5、设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 preparedStatement.setString(1, "hehe"); //6、向数据库发出sql执行查询,查询出结果集 resultSet = preparedStatement.executeQuery(); //7、遍历查询结果集 while(resultSet.next()){ System.out.println(resultSet.getString("id")+" "+resultSet.getString("username")); } } catch (Exception e) { e.printStackTrace(); }finally{ //8、释放资源 if(resultSet!=null){ try { resultSet.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(preparedStatement!=null){ try { preparedStatement.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(connection!=null){ try { connection.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
2.2JDBC存在的问题
1、数据库连接频繁的开启和关闭,会严重影响数据库的性能。
2、代码存在硬编码,分别是数据库部分的硬编码和SQL执行部分的硬编码。
3.MyBatis框架原理
3.1框架图
3.2 分析过程
1、MyBatis配置文件,包括MyBatis的全局配置文件和MyBatis映射文件,其中全局配置文件配置数据源、事务等信息;映射文件配置SQL执行的相关信息。
2、MyBatis通过读取配置文件信息(全局配置文件和映射文件),构造出SqlSessionFactory,即会话工厂。
3、通过SqlSessionFactory,可以创建SqlSession即会话。MyBatis是通过SqlSession来操作数据库的。
4、SqlSession本身不能操作数据库,它通过底层的Executor执行器来操作数据库的。Executor接口有两个实现类,一个是普通执行器,一个是缓存执行器(默认)。
5、Executor执行器要处理的SQL信息是封装到一个底层对象MappedStatement中。该对象包括SQL语句,输入参数映射信息、输出结果集映射信息。其中输入参数和输出结果的映射类型包括Java基本数据类型、POJO(Plain Old Java Objects,普通的 Java对象)对象类型、HashMap集合对象。
4.MyBatis的入门程序
订单商品的案例进行讲解。
4.1需求
对用户信息的增删改查。
1、根据用户ID来查询用户信息
2、根据用户名称来模糊查询用户信息列表
3、添加用户
4、删除用户
5、修改用户
4.2环境的准备
- JDK环境:jdk1.8.0_91
- IDE环境:Eclipse Neon
- 数据库环境:MySQL Server 5.5
- MyBatis版本: 3.4.1
4.2.1 数据库初始化
4.2.1.1 数据库脚本
执行SQL脚本建表
4.2.1.2 数据库表
订单商品中的数据表有四张表,其中入门程序用user表。
User表的结构
4.2.2 下载MyBatis
MyBatis的代码由github进行管理,下载地址:https://github.com/mybatis/mybatis-3/releases
解压mybatis-3.4.1.zip后的目录结构
lib:MyBatis的依赖包
mybatis-3.4.1.jar:MyBatis的核心包
mybatis-3.4.1.pdf:MyBatis的使用手册
4.3工程的搭建
4.3.1 第一步创建Java工程
用Eclipse创建web工程,JDK使用jdk1.8.0_91
4.3.2 第二步 导入Jar包
加入三部分Jar包
1.MyBatis的核心包
2.MyBatis的依赖包
3.MySQL驱动包
4.3.3 第三步:添加log4j.properties文件
该文件用户日志的输出
在MyBatis使用的日志包是log4j,所以添加log4j.properties.
在classpath下创建log4j.properties
在帮助文档中进行拷贝,修改log4j.rootLogger为DEBUG
# Global logging configuration log4j.rootLogger=DEBUG, stdout # MyBatis logging configuration... log4j.logger.org.mybatis.example.BlogMapper=TRACE # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
日志级别在开发阶段设置为DEBUG,在生产阶段设置为INFO或ERROR。
4.4编程的步骤
1、创建实体类,根据需求来创建(参照表结构)。
2、创建全局配置文件SqlMapConfig.xml
3、编写映射文件
4、加载映射文件(在SqlMapConfig.xml中进行加载)
5、编写测试程序,即编写Java代码,连接并操作数据库。
具体思路:
①读取配置文件
②通过SqlSessionFactoryBuilder 创建SqlSessionFactory会话工厂。
③通过SqlSessionFactory创建SqlSession。
④调用Sqlsession的操作数据库的方法。
⑤关闭SqlSession。
4.5 代码开发
4.5.1 创建实体类
命名规范:创建实体类的属性名要和数据库表的列名一致(如果表中的列名带有下划线,那么实体类中对应的属性名采用驼峰式命名法)。
创建User类:
public class User { private int id;//用户ID private String username;//用户名 private String password;//用户密码 private String gender;//性别 private String email;//邮箱 private String telephone;//联系方式 private String introduce;//简介 private String activeCode;//激活码 private String role;// 权限 private int state;//状态 private Date registTime;//注册时间 Setter/getter方法 }
4.5.2 创建SqlMapConfig.xml配置文件
在classpath下,创建SqlMapConfig.xml文件
SqlMapConfig.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> <!-- 配置MyBatis环境信息 --> <environments default="development"> <environment id="development"> <!-- 配置JDBC事务控制,由MyBatis来管理 --> <transactionManager type="JDBC"/> <!-- 配置数据源,采用的MyBatis数据库连接池 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/db_easybuy?useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> </configuration>
4.5.3 需求开发
在classpath下创建sqlmap文件夹,创建User.xml映射文件。
映射文件xml引入dtd的写法可以在入门中进行拷贝
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
4.5.3.1 根据用户ID来查询用户信息
4.5.3.1.1 编写映射文件
<?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"> <!-- namespace:命名空间,它的作用就是SQL进行分类管理,可以理解为SQL的隔离 注意:Mapper代理开发时,namespace有特殊并且重要的作用。 --> <mapper namespace="test"> <!-- 根据ID查询用户 --> <!-- [id]:statement的id,要求在命名空间内是唯一的 [parameterType]:传入参数的Java类型->int [resultType]:查询出的结果集(每一个记录)对应Java类型 [#{}]:表示一个占位符,相当于? [#{id}]:表示该占位符接收参数的名称为id,注意:如果参数为简单数据类型时,#{}里面的参数名称可以使任意定义的。 --> <select id="findUserById" parameterType="int" resultType="com.yaorange.easybuy.pojo.User"> SELECT * FROM USER WHERE id=#{id} </select> </mapper> |
4.5.3.1.2 加载映射文件
在SqlMapConfig.xml文件中,添加以下代码
<!-- 加载mapper文件 --> <mappers> <mapper resource="sqlmap/User.xml"/> </mappers> |
4.5.3.1.3 编写测试程序
@Test public void testFindUserById() throws Exception{
//1.读取全局配置文件 String resource="SqlMapConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件来创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过SqlSessionFactory创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //4.通过SqlSession执行Statement,并返回映射结果 //第一个参数:statement的id,建议使用namespace.statementid来确保唯一 //第二个参数:为传入参数的值,它的类型要和映射文件中对应statement的入参类型一致。 User user = sqlSession.selectOne("test.findUserById", 5); //打印User对象 System.out.println(user); //5.关闭sqlSession对象 sqlSession.close(); } |
4.5.3.2 根据用户名来模糊查询用户信息列表
4.5.3.2.1 编写映射文件
<!-- 根据用户名模糊查询用户信息列表 --> <!-- [${}]:表示拼接SQL字符串 [${value}]:表示拼接的简单数据类型 注意: 1.如果参数为简单数据类型,${}里面的参数名称必须为value 2.${} 会引起sql注入的问题,一般情况不推荐使用,但有些场景必须使用它 --> <select id="findUsersByName" parameterType="String" resultType="com.yaorange.easybuy.pojo.User"> SELECT * FROM USER WHERE username like '%${value}%' </select> |
4.5.3.2.2 加载映射文件
已经加载
4.5.3.2.3 编写测试程序
@Test public void testFindUsersByName() throws Exception{
//1.读取全局配置文件 String resource="SqlMapConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件来创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过SqlSessionFactory创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //4.通过SqlSession执行Statement,并返回映射结果 //第一个参数:statement的id,建议使用namespace.statementid来确保唯一 //第二个参数:为传入参数的值,它的类型要和映射文件中对应statement的入参类型一致。 List<User> users = sqlSession.selectList("test.findUsersByName", "h"); //打印User对象 System.out.println(users); //5.关闭sqlSession对象 sqlSession.close(); } |
错误:
如果查询结果返回为集合,不能用selectOne而应该用selectList
4.5.3.3 添加用户
4.5.3.3.1 编写映射文件
在映射文件添加以下配置
<!-- 因为ID为自动增长,所以插入数据时,可以不添加该列 parameterType 入参类型:com.yaorange.easybuy.pojo.User --> <insert id="insertUser" parameterType="com.yaorange.easybuy.pojo.User"> INSERT INTO USER(username,password,gender,email,telephone,introduce,activeCode,role,state,registTime) VALUES(#{username},#{password},#{gender},#{email},#{telephone},#{introduce},#{activeCode},#{role},#{state},#{registTime}) </insert> |
4.5.3.3.2 加载映射文件
已经加载
4.5.3.3.3 编写测试程序
@Test public void testInsertUser() throws Exception{
//1.读取全局配置文件 String resource="SqlMapConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件来创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过SqlSessionFactory创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //4.通过SqlSession执行Statement,并返回映射结果 //第一个参数:statement的id,建议使用namespace.statementid来确保唯一 //第二个参数:为传入参数的值,它的类型要和映射文件中对应statement的入参类型一致。
User user=new User(); user.setUsername("wowo"); user.setPassword("123456"); user.setEmail("wowo@qq.com"); user.setGender("男"); sqlSession.insert("test.insertUser",user); //进行增删改操作时,一定要提交事务 sqlSession.commit(); //5.关闭sqlSession对象 sqlSession.close(); } |
4.5.3.3.4 主键返回(MySQL的自增主键)
思路:
- MySQL自增主键,是在insert之前mysql 会自动生成一个自增的主键
- 我可以通过MySQL的函数来获取刚插入的自增主键
LAST_INSERT_ID()
- 这个函数是在insert语句之后调用
修改映射文件:
<!-- 添加用户 --> <!-- 因为ID为自动增长,所以插入数据时,可以不添加该列 parameterType 入参类型:com.yaorange.easybuy.pojo.User --> <insert id="insertUser" parameterType="com.yaorange.easybuy.pojo.User"> <!-- [selectKey标签]:通过Select查询生成的主键 --> <!-- [keyProperty]:指定存放生成主键的属性 --> <!-- [keyColumn]:指定主键的列 --> <!-- [resultType]:生成主键对应的Java类型 --> <!-- [order] 指定该查询主键SQL语句执行顺序,相对于insert语句 --> <!-- [ LAST_INSERT_ID()] MySQL的函数,配合Insert语句来使用 --> <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> INSERT INTO USER(username,password,gender,email,telephone,introduce,activeCode,role,state,registTime) VALUES(#{username},#{password},#{gender},#{email},#{telephone},#{introduce},#{activeCode},#{role},#{state},#{registTime}) </insert> |
4.5.3.3.5 主键返回(MySQL 函数UUID)
注意:使用MySQL的UUID函数生成主键,需要修改表中id类型为varchar,长度设置为35
SELECT UUID()
<!-- 添加用户-uuid主键返回 --> <!-- [UUID()} :MySQL的函数,生成的主键是35位的字符串,所以使用它需要修改id类型为字符型 注意:1.order="BEFORE",因为先要生成主键,再执行Insert语句 2.显示的给ID赋值 --> <selectKey keyProperty="id" keyColumn="id" resultType="string" order="BEFORE"> SELECT UUID() </selectKey> INSERT INTO USER(id,username,password,gender,email,telephone,introduce,activeCode,role,state,registTime) VALUES(#{id},#{username},#{password},#{gender},#{email},#{telephone},#{introduce},#{activeCode},#{role},#{state},#{registTime}) </insert> |
4.5.3.4 删除用户
4.5.3.4.1 编写映射文件
<!-- 根据ID删除用户 --> <delete id="deleteUser" parameterType="int"> DELETE FROM USER WHERE id=#{id} </delete> |
4.5.3.4.2 加载映射文件
已配置,无需再配置。
4.5.3.4.3 编写测试程序
@Test public void testDeleteUser() throws Exception{
//1.读取全局配置文件 String resource="SqlMapConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件来创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过SqlSessionFactory创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //4.通过SqlSession执行Statement,并返回映射结果 //第一个参数:statement的id,建议使用namespace.statementid来确保唯一 //第二个参数:为传入参数的值,它的类型要和映射文件中对应statement的入参类型一致。
sqlSession.delete("test.deleteUser",8); //进行增删改操作时,一定要提交事务 sqlSession.commit(); //5.关闭sqlSession对象 sqlSession.close(); } |
4.5.3.5 修改用户
4.5.3.5.1 编写映射文件
<!-- 修改用户的信息 --> <update id="updateUser" parameterType="com.yaorange.easybuy.pojo.User"> UPDATE USER SET telephone=#{telephone},introduce=#{introduce} WHERE id=#{id} </update> |
4.5.3.5.2 加载映射文件
已配置,此处无需再次配置。
4.5.3.5.3 编写测试程序
@Test public void testUpdateUser() throws Exception{ //1.读取全局配置文件 String resource="SqlMapConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //2.根据配置文件来创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过SqlSessionFactory创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //4.通过SqlSession执行Statement,并返回映射结果 //第一个参数:statement的id,建议使用namespace.statementid来确保唯一 //第二个参数:为传入参数的值,它的类型要和映射文件中对应statement的入参类型一致。
/*User user=new User(); user.setId(id);*/ //根据ID查询用户 User user=sqlSession.selectOne("test.findUserById",10); //修改用户信息 user.setTelephone("13245678901"); user.setIntroduce("lalala");
sqlSession.update("test.updateUser", user); //进行增删改操作时,一定要提交事务 sqlSession.commit();
//5.关闭sqlSession对象 sqlSession.close(); } |
4.6 小结
4.6.1 ParameterType和ResultType
parameterType指定输入参数的Java类型,可以填别名或Java类的全限定名(包名+类名).
resultType指定输出结果的Java类型,可以填写别名或Java类的全限定名(包名+类名).
4.6.2 #{}和${}
#{} 相当于预处理语句中的占位符?
#{}里面的参数表示可以接受Java输入的参数名称
#{}可以接受HashMap、简单类型、POJO类型的参数
#{}当接受简单类型参数时,里面可以使value,也可以任意值。
#{}可以防止SQL注入
${}:相当于拼接SQL语句,对传入的值不做任何的解释,原样输出
${}会引起SQL注入,所以我谨慎使用
${}可以接受HashMap、简单类型、POJO类型的参数。
当接受简单类型参数时,${}里面只能是value
4.6.3 selectOne 和selectList
selectOne:只能查询0或1条记录。大于1条记录就会报错
selectList:可以查询0或N条记录
5.MyBatis开发DAO
MyBatis在项目中主要使用的地方就是开发dao(数据访问层)。MyBatis开发dao方法有两种方式,分别是原始dao开发方式,mapper代理的方式(推荐)。
5.1需求
1.根据用户ID来查询用户信息;
2.根据用户名称来模糊查询用户信息列表
3.添加用户
5.2 原始dao开发方式
5.2.1 思路
程序员需要定义dao的接口和dao的实现类。
5.2.2 编程步骤
1.根据需求创建实体
2.编写全局配置文件
3.根据需求编写映射文件
4.加载映射文件
5.编写dao接口
6.编写dao的实现类
7.编写测试代码
5.2.3程序编写
1.2.3.4步已经在第四节完成,参考入门程序
5.2.3.1 开发dao接口
public interface UserDao {
// 1.根据用户ID来查询用户信息; public User findUserById(int id); // 2.根据用户名称来模糊查询用户信息列表 public List<User> findUsersByName(String username); // 3.添加用户 public void insertUser(User user); } |
5.2.3.2 开发dao的实现类
5.2.3.2.1 SqlSession的使用范围
通过入门程序,大家可以知道,在测试代码中,有大量重复代码。所以我们第一反应就是它向办法抽取出共性的部分,但是SqlSession、SqlSessionFactory、SqlSessionFactoryBuilder有着各自的生明周期,因为这些类各自的生命周期不同,抽取时要有针对性的处理。
所以我在抽取之前,了解一下他们三个各自的生命周期。
1.SqlSessionFactoryBuilder
它的作用只是通过配置文集或者注解的方式来创建SqlSessionFactory,所以只要创建了SqlSessionFactory对象,它就可以被销毁了。所以,它的生命周期是在方法之内。
2.SqlSessionFactory
它的作用用户创建Sqlsession的工程,工程一旦创建,除非应用停掉,不要销毁。所以,它的生命周期是在应用范围内有效。这里可以使用单例模式来管理。注意,在以后学习Spring框架,用Spring整合MyBatis,最好方式就是用把SqlSessionFactory的创建交给Spring容器来做单例的管理.
3.SqlSession
sqlSession是一个面向用户(程序员)的接口,它默认的实现类是DefaultSqlSession。
MyBatis是通过对SqlSession的操作来完成对数据库的处理。SqlSession中不仅包含了对处理SQL的信息,还包括了一些数据信息。所以它是线程不安全的,因此他的最佳声明周期是在方法体之内。
5.2.3.2.2 Dao实现类的代码
需要向dao实现类注入SqlSessionFactory,在方法体内通过SqlSessionFactory创建SqlSession对象。要注意SqlSession和SqlSessionFactory的生命周期。
public class UserDaoImpl implements UserDao {
// 注入SqlSessionFactory private SqlSessionFactory sqlSessionFactory;
// 方式一通过构造函数 public UserDaoImpl(SqlSessionFactory sqlSessionFactory) { this.sqlSessionFactory = sqlSessionFactory; }
// 方式二通过setter方法 public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { this.sqlSessionFactory = sqlSessionFactory; }
@Override public User findUserById(int id) { // 通过工厂,在方法体内获取SqlSession,这样就可以避免线程不安全 SqlSession sqlSession = sqlSessionFactory.openSession(); // 调用SqlSession的查询方法 User user = sqlSession.selectOne("test.findUserById", id); // 关闭SqlSession sqlSession.close(); return user; }
@Override public List<User> findUsersByName(String username) { // 通过工厂,在方法体内获取SqlSession,这样就可以避免线程不安全 SqlSession sqlSession = sqlSessionFactory.openSession(); // 调用SqlSession的查询方法 List<User> users = sqlSession.selectList("test.findUsersByName", username); // 关闭SqlSession sqlSession.close(); return users; }
@Override public void insertUser(User user) {
// 通过工厂,在方法体内获取SqlSession,这样就可以避免线程不安全 SqlSession sqlSession = sqlSessionFactory.openSession(); // 调用SqlSession的查询方法 sqlSession.insert("test.insertUser",user); //提交事务 sqlSession.commit(); // 关闭SqlSession sqlSession.close(); }
}
|
5.2.3.3编写测试代码
public class TestUserDao {
private SqlSessionFactory sqlSessionFactory;
@Before public void setUp() throws Exception { //实例化SqlSessionFactory //1.读取配置文件 String resource="SqlMapConfig.xml"; //2.获取输入流 InputStream inputStream = Resources.getResourceAsStream(resource); //3.根据配置文件来实例化SqlSessionFactory对象 sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream); }
@Test public void testFindUserById() { //创建UserDao对象 UserDao dao=new UserDaoImpl(sqlSessionFactory); //调用UserDao对象方法 User user=dao.findUserById(7); System.out.println(user);
}
@Test public void testFindUsersByName() {
UserDaoImpl dao=new UserDaoImpl(); dao.setSqlSessionFactory(sqlSessionFactory); List<User> users=dao.findUsersByName("h"); System.out.println(users);
}
@Test public void testInsertUser() { fail("Not yet implemented"); }
}
|
工具类MyBatisUtil
public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory; static{ String resource="SqlMapConfig.xml"; try { InputStream inputStream=Resources.getResourceAsStream(resource); sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); }
}
public static SqlSessionFactory getSqlSessionFactory(){ return sqlSessionFactory; } } |
public class TestUserDao {
private SqlSessionFactory sqlSessionFactory;
@Before public void setUp() throws Exception { /* * //实例化SqlSessionFactory //1.读取配置文件 String resource="SqlMapConfig.xml"; * //2.获取输入流 InputStream inputStream = * Resources.getResourceAsStream(resource); * //3.根据配置文件来实例化SqlSessionFactory对象 sqlSessionFactory=new * SqlSessionFactoryBuilder().build(inputStream); */ sqlSessionFactory = MyBatisUtil.getSqlSessionFactory(); }
@Test public void testFindUserById() { // 创建UserDao对象 UserDao dao = new UserDaoImpl(sqlSessionFactory); // 调用UserDao对象方法 User user = dao.findUserById(7); System.out.println(user);
}
@Test public void testFindUsersByName() {
UserDaoImpl dao = new UserDaoImpl(); dao.setSqlSessionFactory(sqlSessionFactory); List<User> users = dao.findUsersByName("h"); System.out.println(users);
}
@Test public void testInsertUser() { // 创建UserDao对象 UserDao dao = new UserDaoImpl(sqlSessionFactory);
User user=new User(); user.setUsername("rose"); user.setPassword("123456"); user.setGender("女");
dao.insertUser(user);
}
} |
5.2.4 问题的总结
原始的dao开发存在一些问题
- 存在一定的模板代码。比如,通过SqlSessionFactory创建SqlSession,调用SqlSession的方法来操作数据库,关闭SqlSession的方法。
- 存在一些硬编码:调用SqlSession的方法操作数据库时,需要指定statement的ID,这里存在了一些硬编码
5.3 Mapper代理开发方式(推荐)
Mapper代理的开发方式,程序员只需要编写Mapper接口(相当于dao接口)即可。MyBatis会自动的为Mapper接口生成动态代理类。
不过要实现Mapper代理的开发方式,需要遵循一些开发规范。(约定强于编码)
5.3.1开发规范
1.Mapper接口的全限定名和Mapper映射文件的namespace的值相同。
2.Mapper接口的方法名称和Mapper映射文件的statement的id相同
3.Mapper接口的方法参数只能有一个,且类型和Mapper映射文件中的statement的parameterType的值保持一致。
4.Mapper接口的返回值类型要和Mapper映射文件中statement的resultType值或resultMap中的type值保持一致。
通过规范式的开发Mapper接口,可以解决原始dao的开发中存在的问题:
1.模板代码已经去掉
2.剩下去不掉的操作数据库的代码,其实就是一行代码,这些代码中的硬编码部分,通过第一个和第二个约定就可以解决。
5.3.2 编程步骤
1.根据需求创建实体
2.编写全局配置文件
3.根据需求编写映射文件
4.加载映射文件
5.编写Mapper接口
6.编写测试代码
5.3.3 程序编写
步骤中的 1-2步在入门程序中进行了编写,参考入门程序
5.3.3.1 编写Mapper映射文件
重写定义mapper映射文件UserMapper.xml内容同User.xml一样(除命名空间),放到新建的包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"> <!-- namespace:命名空间,它的作用就是SQL进行分类管理,可以理解为SQL的隔离 注意:Mapper代理开发时,它的值必须等于对应Mapper接口的全限定名。 --> <mapper namespace="com.yaorange.easybuy.mapper.UserMapper"> <!-- 根据ID查询用户 --> <!-- [id]:statement的id,要求在命名空间内是唯一的 [parameterType]:传入参数的Java类型->int [resultType]:查询出的结果集(每一个记录)对应Java类型 [#{}]:表示一个占位符,相当于? [#{id}]:表示该占位符接收参数的名称为id,注意:如果参数为简单数据类型时,#{}里面的参数名称可以使任意定义的。 --> <select id="findUserById" parameterType="int" resultType="com.yaorange.easybuy.pojo.User"> SELECT * FROM USER WHERE id=#{id} </select>
<!-- 根据用户名模糊查询用户信息列表 --> <!-- [${}]:表示拼接SQL字符串 [${value}]:表示拼接的简单数据类型 注意: 1.如果参数为简单数据类型,${}里面的参数名称必须为value 2.${} 会引起sql注入的问题,一般情况不推荐使用,但有些场景必须使用它 --> <select id="findUsersByName" parameterType="String" resultType="com.yaorange.easybuy.pojo.User"> SELECT * FROM USER WHERE username like '%${value}%' </select>
<!-- 添加用户 --> <!-- 因为ID为自动增长,所以插入数据时,可以不添加该列 parameterType 入参类型:com.yaorange.easybuy.pojo.User --> <insert id="insertUser" parameterType="com.yaorange.easybuy.pojo.User"> <!-- [selectKey标签]:通过Select查询生成的主键 --> <!-- [keyProperty]:指定存放生成主键的属性 --> <!-- [keyColumn]:指定主键的列 --> <!-- [resultType]:生成主键对应的Java类型 --> <!-- [order] 指定该查询主键SQL语句执行顺序,相对于insert语句 --> <!-- [ LAST_INSERT_ID()] MySQL的函数,配合Insert语句来使用 --> <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> <!-- 添加用户-uuid主键返回 --> <!-- [UUID()} :MySQL的函数,生成的主键是35位的字符串,所以使用它需要修改id类型为字符型 注意:1.order="BEFORE",因为先要生成主键,再执行Insert语句 2.显示的给ID赋值 --> <!-- <selectKey keyProperty="id" keyColumn="id" resultType="string" order="BEFORE"> SELECT UUID() </selectKey> --> INSERT INTO USER(id,username,password,gender,email,telephone,introduce,activeCode,role,state,registTime) VALUES(#{id},#{username},#{password},#{gender},#{email},#{telephone},#{introduce},#{activeCode},#{role},#{state},#{registTime}) </insert>
<!-- 根据ID删除用户 --> <delete id="deleteUser" parameterType="int"> DELETE FROM USER WHERE id=#{id} </delete>
<!-- 修改用户的信息 --> <update id="updateUser" parameterType="com.yaorange.easybuy.pojo.User"> UPDATE USER SET telephone=#{telephone},introduce=#{introduce} WHERE id=#{id} </update> </mapper> |
5.3.3.2 加载Mapper映射文件
<!-- 加载mapper文件 --> <mappers> <mapper resource="sqlmap/User.xml"/> <mapper resource="mapper/UserMapper.xml"/> </mappers> |
5.3.3.3 编写Mapper接口
public interface UserMapper { // 1.根据用户ID来查询用户信息; public User findUserById(int id); // 2.根据用户名称来模糊查询用户信息列表 public List<User> findUsersByName(String username); // 3.添加用户 public void insertUser(User user); } |
5.3.3.4 编写测试代码
public class TestUserMapper {
private SqlSessionFactory sqlSessionFactory;
@Before public void setUp() throws Exception { /* * //实例化SqlSessionFactory //1.读取配置文件 String resource="SqlMapConfig.xml"; * //2.获取输入流 InputStream inputStream = * Resources.getResourceAsStream(resource); * //3.根据配置文件来实例化SqlSessionFactory对象 sqlSessionFactory=new * SqlSessionFactoryBuilder().build(inputStream); */ sqlSessionFactory = MyBatisUtil.getSqlSessionFactory(); }
@Test public void testFindUserById() {
// 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 调用Mapper对象的方法 User user = userMapper.findUserById(7); System.out.println(user); // 关闭SqlSession sqlSession.close(); }
@Test public void testFindUsersByName() { // 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 调用Mapper对象的方法 List<User> users = userMapper.findUsersByName("h"); System.out.println(users); // 关闭SqlSession sqlSession.close(); }
@Test public void testInsertUser() { // 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user=new User(); user.setUsername("andy"); user.setPassword("123456"); user.setGender("男"); userMapper.insertUser(user); sqlSession.commit(); // 关闭SqlSession sqlSession.close(); }
} |
6.MyBatis全局的配置文件
SqMaplConfig.xml是MyBatis全局配置文件,它的名称可以任意命名的。
6.1全局配置的内容
在全局配置文件中可以configuration 配置的信息,(顺序不能修改)
- properties 属性
- settings 全局设置
- typeAliases 类型命名
- typeHandlers 类型处理器
- objectFactory 对象工厂
- plugins 插件
- environments 环境
n environment 环境变量
u transactionManager 事务管理器
u dataSource 数据源
- databaseIdProvider 数据库厂商标识
- mappers 映射器
6.2 常用配置详解
6.2.1 properties 属性
SqlMapConfig.xml文件中引用Java属性文件中配置信息
定义db.properties
db.driver=com.mysql.jdbc.Driver db.url=jdbc:mysql://127.0.0.1:3306/db_easybuy?useUnicode=true&characterEncoding=utf8 db.user=root db.password=root |
在SqlMapConfig.xml中使用properties标签
<configuration> <!-- 通过properties标签,读取Java配置文件的信息 --> <properties resource="db.properties"></properties> <!-- 配置MyBatis环境信息 --> <environments default="development"> <environment id="development"> <!-- 配置JDBC事务控制,由MyBatis来管理 --> <transactionManager type="JDBC"/> <!-- 配置数据源,采用的MyBatis数据库连接池 --> <dataSource type="POOLED"> <property name="driver" value="${db.driver}"/> <property name="url" value="${db.url}"/> <property name="username" value="${db.user}"/> <property name="password" value="${db.password}"/> </dataSource> </environment> </environments>
<!-- 加载mapper文件 --> <mappers> <mapper resource="sqlmap/User.xml"/> <mapper resource="mapper/UserMapper.xml"/> </mappers> </configuration> |
使用${},可以引用已经加载Java配置文件的属性信息。
注意:MyBatis将按照以下的顺序加载属性:
- Properties标签体内的<property>的属性首先读取
- Properties引用Java属性文件中属性会被读取,如果发现了上面同名的属性名,那后面的会覆盖前面的值
- parameterType接收的值会被最后读取,如果发现上面已经有同名的属性,那后面的会覆盖前面的值。
所以,MyBatis读取属性的顺序有高到低分别是parameterType接收的属性值,properties引用的属性,Properties标签内定义的属性。
6.2.2settings
MyBatis的全局配置参数,全局参数会影响MyBatis的运行行为。
设置参数 |
描述 |
有效值 |
默认值 |
cacheEnabled |
该配置影响的所有映射器中配置的缓存的全局开关。 |
true | false |
true |
lazyLoadingEnabled |
延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 |
true | false |
false |
aggressiveLazyLoading |
当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。 |
true | false |
true |
multipleResultSetsEnabled |
是否允许单一语句返回多结果集(需要兼容驱动)。 |
true | false |
true |
useColumnLabel |
使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 |
true | false |
true |
useGeneratedKeys |
允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 |
true | false |
False |
autoMappingBehavior |
指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 |
NONE, PARTIAL, FULL |
PARTIAL |
autoMappingUnknownColumnBehavior |
Specify the behavior when detects an unknown column (or unknown property type) of automatic mapping target.
|
NONE, WARNING, FAILING |
NONE |
defaultExecutorType |
配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 |
SIMPLE REUSE BATCH |
SIMPLE |
defaultStatementTimeout |
设置超时时间,它决定驱动等待数据库响应的秒数。 |
Any positive integer |
Not Set (null) |
defaultFetchSize |
Sets the driver a hint as to control fetching size for return results. This parameter value can be override by a query setting. |
Any positive integer |
Not Set (null) |
safeRowBoundsEnabled |
允许在嵌套语句中使用分页(RowBounds)。 If allow, set the false. |
true | false |
False |
safeResultHandlerEnabled |
允许在嵌套语句中使用分页(ResultHandler)。 If allow, set the false. |
true | false |
True |
mapUnderscoreToCamelCase |
是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 |
true | false |
False |
localCacheScope |
MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 |
SESSION | STATEMENT |
SESSION |
jdbcTypeForNull |
当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 |
JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER |
OTHER |
lazyLoadTriggerMethods |
指定哪个对象的方法触发一次延迟加载。 |
A method name list separated by commas |
equals,clone,hashCode,toString |
defaultScriptingLanguage |
指定动态 SQL 生成的默认语言。 |
A type alias or fully qualified class name. |
org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver |
callSettersOnNulls |
指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。 |
true | false |
false |
logPrefix |
指定 MyBatis 增加到日志名称的前缀。 |
Any String |
Not set |
logImpl |
指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 |
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING |
Not set |
proxyFactory |
指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 |
CGLIB | JAVASSIST |
JAVASSIST (MyBatis 3.3 or above) |
vfsImpl |
Specifies VFS implementations |
Fully qualified class names of custom VFS implementation separated by commas. |
Not set |
useActualParamName |
Allow referencing statement parameters by their actual names declared in the method signature. To use this feature, your project must be compiled in Java 8 with -parameters option. (Since: 3.4.1) |
true | false |
|
6.2.3typeAliases
类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
在没有使用别名前,在映射文件中直接使用类的简名称,会抛以下异常。
6.2.3.1 MyBatis支持的别名
已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,需要注意的是由基本类型名称重复导致的特殊处理。
别名 |
映射的类型 |
_byte |
byte |
_long |
long |
_short |
short |
_int |
int |
_integer |
int |
_double |
double |
_float |
float |
_boolean |
boolean |
string |
String |
byte |
Byte |
long |
Long |
short |
Short |
int |
Integer |
integer |
Integer |
double |
Double |
float |
Float |
boolean |
Boolean |
date |
Date |
decimal |
BigDecimal |
bigdecimal |
BigDecimal |
object |
Object |
map |
Map |
hashmap |
HashMap |
list |
List |
arraylist |
ArrayList |
collection |
Collection |
iterator |
Iterator |
6.2.3.2 自定义别名
<!-- 配置别名 --> <typeAliases> <!-- 单个定义别名 --> <!-- [type]:类的全限定名称 [alias]:别名 --> <!-- <typeAlias type="com.yaorange.easybuy.pojo.User" alias="User"/> --> <!-- 批量设置别名(推荐) --> <!-- [name]:指定批量定义别名类所在包名,别名为类名(首字母大小写都可以) --> <package name="com.yaorange.easybuy.pojo"/> </typeAliases> |
6.2.4 Mapper
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要定义 SQL 映射语句了。但是首先我们需要告诉 MyBatis 到哪里去找到这些语句。 Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。你可以使用相对于类路径的资源引用, 或完全限定资源定位符(包括 file:/// 的 URL),或类名和包名等。例如:
6.2.4.1 <mapper resource=""/>
<!-- Using classpath relative resources --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers>
|
6.2.4.2<mapper url=""/>
<!-- Using url fully qualified paths --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <mapper url="file:///var/mappers/BlogMapper.xml"/> <mapper url="file:///var/mappers/PostMapper.xml"/> </mappers>
|
6.2.4.3<mapper class=""/>
<!-- Using mapper interface classes -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
注意:此种引用要求Mapper接口和Mapper映射文件名称要相同,且放在同一个目录下;
6.2.4.3<package name=""/> (推荐)
<!-- Register all interfaces in a package as mappers -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
注意:此种引用要求Mapper接口和Mapper映射文件名称要相同,且放在同一个目录下;
如果没有在同一个目录,会抛此异常
7.MyBatis的映射文件
7.1 输入映射
7.1.1 ParameterType
指定输入参数的类型(Java),可以使用类的全限定名称或别名。它可以接收简单类型、POJO、HashMap。
7.1.1.1 传递简单数据类型
参照入门程序:根据ID查询用户
7.1.1.2 传递POJO对象
参照入门程序:添加用户
7.1.1.3 传递POJO包装对象
开发中通过POJO传递查询条件,查询条件是综合查询条件,不仅包含用户查询的条件,还包括其他的查询条件(比如将用户购买商品信息也作为查询条件),这是可以使用包装对象传递输入参数。
7.1.1.3.1 需求
综合查询用户信息,需要传入查询条件复杂,比如(用户信息、订单信息,商品信息)
7.1.1.3.2 定义包装对象
一般User.java类要和数据表字段一致,最好不要在User.java中添加其他字段。所以要针对要扩展的实体类,我需要创建一个扩展类,来继承它。
public class UseQueryVo { //用户信息 private User user; //商品信息 //订单信息
public User getUser() { return user; }
public void setUser(User user) { this.user = user; }
}
|
7.1.1.3.2 编写Mapper接口
在Mapper接口中添加以下代码
//通过包装类来进行复杂的用户信息综合查询 public List<User> findUserList(UserQueryVO userQueryVO); |
7.1.1.3.3 编写Mapper的映射文件
<!-- 通过包装类来进行复杂的用户信息的综合查询 --> <select id="findUserList" parameterType="userQueryVO" resultType="user"> SELECT * FORM USER WHERE username like '%${user.username}%' AND gender=#{user.gender} </select> |
注意:入参类型变为UserQueryVO、结果集为User,#{}里面的参数为UserQueryVO对象中的User属性的username和gender子属性。
7.1.1.3.4 编写测试代码
@Test public void testFindUserList() {
// 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserQueryVO userQueryVO = new UserQueryVO(); User user=new User(); user.setUsername("h"); user.setGender("女"); userQueryVO.setUser(user); // 调用Mapper对象的方法 List<User> users = userMapper.findUserList(userQueryVO); System.out.println(users); // 关闭SqlSession sqlSession.close(); } |
7.1.1.4 传递HashMap
同传递POJO对象一样,map的key相当于POJO的属性名
7.1.1.4.1 定义Mapper接口
//根据HashMap进行复杂的用户信息综合查询 public List<User> findUsersByHashMap(HashMap<String,String> map); |
7.1.1.4.2 映射文件
<!-- 通过HashMap进行复杂的用户信息的综合查询 --> <select id="findUsersByHashMap" parameterType="hashmap" resultType="user"> SELECT * FROM USER WHERE username like '%${username}%' AND gender=#{gender} </select> |
红色标注的是HashMap的key。
7.1.1.4.3 编写测试代码
@Test public void testFindUsersByHashMap() {
// 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
HashMap<String,String> map=new HashMap<>(); map.put("username", "h"); map.put("gender", "男");
// 调用Mapper对象的方法 List<User> users = userMapper.findUsersByHashMap(map); System.out.println(users); // 关闭SqlSession sqlSession.close(); } |
7.2输出映射
7.2.1 resultType
从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用。
7.2.1.1 使用方法
使用resultType进行结果映射时,查询的列名和映射的POJO属性名完全一致,该列才能映射成功。
如果查询的列名和映射的POJO属性名全不一致,则不会创建POJO对象
如果查询的列名和有映射的POJO属性名有一个一致,就会创建POJO对象
7.2.1.2 输出简单类型
当输出只有一列时,可以使用ResultType指定简单类型作为输出结果类型。
7.2.1.2.1 需求
综合查询用户总数,需要传入查询复杂的条件,比如(用户信息、订单信息、商品信息)
7.2.1.2.1 编写Mapper映射文件
<!-- 综合查询用户总数 --> <select id="findUserCount" parameterType="userQueryVO" resultType="int"> SELECT count(*) FROM USER WHERE username like '%${user.username}%' AND gender=#{user.gender} </select> |
输出结构类型->int
7.2.1.2.2 编写Mapper接口
//查询用户的总数 public int findUserCount(UserQueryVO userQueryVO); |
7.2.1.2.2 编写测试代码
@Test public void testFindUserCount() {
// 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserQueryVO userQueryVO = new UserQueryVO(); User user=new User(); user.setUsername("h"); user.setGender("女"); userQueryVO.setUser(user); // 调用Mapper对象的方法
int count=userMapper.findUserCount(userQueryVO); System.out.println(count); // 关闭SqlSession sqlSession.close(); } |
7.2.1.3 输出POJO单个对象和列表
输出单个POJO对象和POJO的列表(存放POJO对象)时,Mapper映射文件中的resultType得类型是一致,Mapper接口的方法返回值不同。
7.2.1.3.1 需求
根据ID查询用户
根据用户名查询用户列表
7.2.1.3.2 编写Mapper映射文件
// 1.根据用户ID来查询用户信息; public User findUserById(int id); |
<select id="findUsersByName" parameterType="String" resultType="user"> SELECT * FROM USER WHERE username like '%${value}%' </select> |
7.2.1.3.2 编写Mapper接口
1.输出单个POJO对象
// 1.根据用户ID来查询用户信息; public User findUserById(int id); |
2.输出POJO的列表
// 2.根据用户名称来模糊查询用户信息列表 public List<User> findUsersByName(String username); |
总结:同样的Mapper返回结果类型的值“user”,返回单个对象和对象列表时,Mapper接口在生成动态代理的时候,会根据返回值类型,决定是调用selectOne方法还是selectList方法。
7.2.2 resultMap
外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,对其有一个很好的理解的话,许多复杂映射的情形都能迎刃而解。
进行高级结果映射。
7.2.2.1 使用方法
如果查询出来的列名和POJO对象的属性名不一致,通过定义一resultMap将列名和POJO属性名之间做一个映射关系。
1.定义resultMap
2.使用resultMap作为statement的输出映射类型
注:resultType和resultMap不能同时使用
7.2.2.2 需求
把下面的SQL输出结果集进行映射
SELECT id id1,username username1,gender,email email1 FROM USER WHERE id=#{id}
7.2.2.3 编写Mapper映射文件
定义resultMap
<!-- 定义resultMap --> <!-- [id]:定义resultMap的唯一标识 [type]:定义该resultMap最终映射的POJO对象 [id标签]:映射结果集的唯一标识符,如果是多个字段联合唯一,则定义多个id标签 [result标签]:映射结果集的普通列 [column]:SQL查询的列名,如果列有别名,则该处填写别名 [property]:POJO对象属性名 --> <resultMap type="user" id="userResultMap"> <!-- 配置结果集的唯一标识符 --> <id column="id1" property="id"/> <result column="username1" property="username"/> <result column="gender" property="gender"/> <result column="email1" property="email"/> </resultMap>
|
定义statement,输出结果类型通过resultMap属性指定上面定义userResultMap
<select id="findUserByIdResultMap" parameterType="int" resultMap="userResultMap"> SELECT id id1,username username1,gender,email email1 FROM USER WHERE id=#{id} </select> |
7.2.2.4 编写Mapper接口
public User findUserByIdResultMap(int id); |
7.2.2.4 编写测试程序
@Test public void testFindUserByIdResultMap() {
// 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 调用Mapper对象的方法 User user = userMapper.findUserByIdResultMap(7); System.out.println(user); // 关闭SqlSession sqlSession.close(); } |
7.3 动态SQL(重点)
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦。拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
通常使用动态 SQL 不可能是独立的一部分,MyBatis 当然使用一种强大的动态 SQL 语言来改进这种情形,这种语言可以被用在任意的 SQL 映射语句中。
动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多的元素需要来了解。MyBatis 3 大大提升了它们,现在用不到原先一半的元素就可以了。MyBatis 采用功能强大的基于 OGNL 的表达式来消除其他元素。
if
choose (when, otherwise)
trim (where, set)
Foreach
7.3.1 if和where
7.3.1.1 需求
根据性别和状态查询用户列表
7.3.1.2 编写映射文件
<!-- 通过包装类来进行复杂的用户信息的综合查询 --> <select id="findUserListByGenderAndState" parameterType="userQueryVO" resultType="user"> SELECT * FROM USER <!--[where标签] 默认为去掉第一个AND ,如果没有参数,则把自己干掉 --> <where> <!-- [if标签]:可以对输入的条件进行判断 [test属性]:进行条件的判断 --> <if test="user.gender!=null and user.gender!=''"> AND gender=#{user.gender} </if> <if test="user.state!=null and user.state!=0"> AND state=#{user.state} </if> </where> </select> |
7.3.1.3 编写Mapper接口
//根据性别和状态进行查询用户列表 public List<User> findUserListByGenderAndState(UserQueryVO userQueryVO); |
7.3.1.4 编写测试程序
@Test public void testUserListByGenderAndState() {
// 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserQueryVO userQueryVO = new UserQueryVO(); User user=new User(); /*user.setState(1); user.setGender("");*/ userQueryVO.setUser(user); // 调用Mapper对象的方法 List<User> users = userMapper.findUserListByGenderAndState(userQueryVO); System.out.println(users); // 关闭SqlSession sqlSession.close(); } |
注意:根据传入条件不同,生成SQL句不同
7.3.2SQL片段
这个元素可以被用来定义可重用的 SQL 代码段,可以包含在其他语句中。它可以被静态地(在加载参数) 参数化. 不同的属性值通过包含的实例变化. 比如:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
这个 SQL 片段可以被包含在其他语句中,例如:
<select id="selectUsers" resultType="map"> select <include refid="userColumns"><property name="alias" value="t1"/></include>, <include refid="userColumns"><property name="alias" value="t2"/></include> from some_table t1 cross join some_table t2</select>
|
属性值可以用于包含的refid属性或者包含的字句里面的属性值,例如:
<sql id="sometable"> ${prefix}Table</sql> <sql id="someinclude"> from <include refid="${include_target}"/></sql> <select id="select" resultType="map"> select field1, field2, field3 <include refid="someinclude"> <property name="prefix" value="Some"/> <property name="include_target" value="sometable"/> </include></select> |
7.3.2.1 定义SQL片段
使用sql标签来定义一个SQL片段
<!-- 定义SQL片段 --> <!-- [sql标签]:定义SQL片段 [id]:SQL片段唯一标识 建议: 1.SQL片段中内容最好以单表来定义 2.如果是查询字段,不要包含select 3.如果是条件语句,不要包含where --> <sql id="where_clause"> <!-- [if标签]:可以对输入的条件进行判断 [test属性]:进行条件的判断 --> <if test="user.gender!=null and user.gender!=''"> AND gender=#{user.gender} </if> <if test="user.state!=null and user.state!=0"> AND state=#{user.state} </if> </sql> |
7.3.2.1 引入SQL片段
<!-- 通过包装类来进行复杂的用户信息的综合查询 --> <select id="findUserListByGenderAndState" parameterType="userQueryVO" resultType="user"> SELECT * FROM USER <!-- [where标签] 默认为去掉第一个AND ,如果没有参数,则把自己干掉 --> <where> <!-- inlude标签 引入SQL片段 [refid] 参考前面定义的sql标签的id --> <include refid="where_clause"></include>
</where> </select> |
<select id="findUserCountByGenderAndState" parameterType="userQueryVO" resultType="int"> SELECT count(*) FROM USER <where> <include refid="where_clause"></include> </where> </select> |
7.3.3 foreach
动态 SQL 的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。foreach 元素的功能是非常强大的,它允许你指定一个集合,声明可以用在元素体内的集合项和索引变量。它也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。
注意 你可以将任何可迭代对象(如列表、集合等)和任何的字典或者数组对象传递给foreach作为集合参数。当使用可迭代对象或者数组时,index是当前迭代的次数,item的值是本次迭代获取的元素。当使用字典(或者Map.Entry对象的集合)时,index是键,item是值。
7.3.3.1 传递POJO对象中的List集合
7.3.3.1.1 需求
在用户查询列表和查询总数的statement中增加多个ID的查询
7.3.3.1.2 SQL
select * from user where id in(5,10,13)
7.3.3.1.3 定义POJO中List类型的属性
public class UserQueryVO { //用户信息 private User user; private List<Integer> idList;
//商品信息 //订单信息
public List<Integer> getIdList() { return idList; }
public void setIdList(List<Integer> idList) { this.idList = idList; } |
7.3.3.1.4 编写Mapper映射文件
<if test="idList!=null and idList.size>0"> AND id In <!-- [foreach标签] 表示一个foreach循环 [collection]:集合参数的名称,如果是直接插入的集合参数,则该参数名称只能为list [item]:每次遍历出来的对象 [open]:开始遍历时拼接的字符串 [close]:结束遍历后拼接的字符串 [separator]:遍历出每个对象之间需要拼接的字符 --> <foreach collection="idList" item="id" open="(" close=")" separator=","> #{id} </foreach> </if> |
7.3.3.1.4 编写Mapper接口
Mapper接口没有变化
7.3.3.1.5 编写测试程序
@Test public void testUserListByGenderAndState() {
// 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserQueryVO userQueryVO = new UserQueryVO(); User user=new User(); /*user.setState(1); user.setGender("女");*/ Integer[] ids=new Integer[]{5,10,13}; userQueryVO.setIdList(Arrays.asList(ids)); userQueryVO.setUser(user); // 调用Mapper对象的方法 List<User> users = userMapper.findUserListByGenderAndState(userQueryVO); System.out.println(users); // 关闭SqlSession sqlSession.close(); } |
7.3.3.2 直接传递List集合(练习)
7.3.3.1.1 需求
在用户查询列表和查询总数的statement中增加多个ID的查询
7.3.3.1.2 SQL
select * from user where id in(5,10,13)
7.3.3.1.3 编写Mapper映射文件
<select id="findUsersByIdList" parameterType="list" resultType="user"> SELECT * FORM USER <where> <if test="list!=null and list.size>0"> <!-- [foreach标签] 表示一个foreach循环 [collection]:集合参数的名称,如果是直接插入的集合参数,则该参数名称只能为list [item]:每次遍历出来的对象 [open]:开始遍历时拼接的字符串 [close]:结束遍历后拼接的字符串 [separator]:遍历出每个对象之间需要拼接的字符 --> <foreach collection="list" item="id" open="AND ID IN(" close=")" separator=","> #{id} </foreach> </if> </where> </select> |
7.3.3.1.4 编写Mapper接口
//根据ID集合来查询用户 public List<User> findUsersByIdList(List<Integer> idList); |
7.3.3.1.5 编写测试程序
@Test public void testFindUsersByIdList() {
// 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<Integer> idList=new ArrayList<Integer>(); idList.add(5); idList.add(10); idList.add(13); List<User> users = userMapper.findUsersByIdList(idList); System.out.println(users); // 关闭SqlSession sqlSession.close(); } |
8.MyBatis 应用场景
MyBatis的技术特点
1.通过直接编写SQL语句,可以直接对SQL进行性能优化;
2.学习门槛较低,学习成本低。只要有SQL基础,就可以学习MyBatis,而且很容易上手
3.由于直接编写SQL语句,所以灵活多变,代码维护性更好。
4.不能支持数据库无关性,即数据库发生变更,要写多套代码进行支持,移植性不好。
5.需要编写结果映射。
MyBatis应用场景
需求多变的互联网项目中,例如电商项目。
9.关系查询映射
9.1 分析数据模型
9.1.1思路
1.每张表记录的数据内容
分模块对每张表记录的内容进行熟悉,相当于大家学习系统需求(了解系统的业务及功能)的过程。
2.每张表重要的字段
主键、外键、非空约束
3.数据库级别表与表的关系
外键关系
4.表与表之间的业务关系
在分析表与表之间的业务关系时一定要建立。(在某个业务意义基础上去分析)
9.1.2 图表分析
9.1.3 数据库表之间有外键关系的业务关系
User和order
User->order: 一个用户可以创建多个订单,一对多
Order->User:一个订单只能一个用户创建,一对一
Order和order_item
Order->order_item:一个订单可以包括多个订单项(明细),因为一个订单可以购买多个商品,每个商品的购买信息存在order_item中,一对多。
Order_item->order:一个订单项只能包括在一个订单中,一对一。
Order_item和prodcut
Order_item->product :一个订单项只对应一个商品的信息,一对一。
Product->order_item:一个商品可以包括在多个订单项中,一对多
9.1.4 数据库表之间没有有外键关系的业务关系
Order和product
这两张表没有直接的外键关系,通过业务及数据库的间接关系分析出他们是多对多的关系。
Order->order_item->product:一个订单可以有多个订单项,一个订单项对应一个商品,所以一个订单对应多个商品。
Product->order_item->order:一个商品可以对应多个订单项,一个订单项对应一个订单,所以一个商品对应多个订单。
User 和product
这两张表没有直接的外键关系,通过业务及数据库的间接关系分析出他们是多对多的关系。
User->order->order_item->product:一个用户有多个订单,一个订单有多个订单项,一个订单项对应一个商品,所以一个用户对应多个商品。
Product->order_item->order->User:一个商品对应多个订单项,一个订单项对应一个订单,一个订单对应一个用户,所以一个商品对应多个用户。
9.2 一对一查询
9.2.1 需求
查询订单信息,关联查询订单所属的用户信息。
9.2.2 SQL语句
确定查询的主表:订单表
确定查询的关联表:用户表
关联查询:外连接还是内连接
SELECT o.id, o.money, o.ordertime, o.paystate, o.receiverAddress, o.receiverName, o.receiverPhone, u.username FROM `order` o INNER JOIN USER u ON o.userid = u.id |
9.2.3 resultType
复杂查询时,单表对应的实体类已不能满足输出结果集的映射。所以要根据需求建立一个扩展类来作为resultType的类型
9.2.3.1 创建实体类
package com.yaorange.easybuy.pojo;
import java.util.Date;
/** * 创建Order实体 * @author tosit * */
public class Order {
private String id;//订单编号 private double money;//订单金额 private String receiverAddress;//收件人地址 private String receiverName;//收件人姓名 private String receiverPhone;//收件人联系方式 private int orderState;//订单状态 private Date orderTime;//订单时间 private int userId; public String getId() { return id; } public void setId(String id) { this.id = id; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } public String getReceiverAddress() { return receiverAddress; } public void setReceiverAddress(String receiverAddress) { this.receiverAddress = receiverAddress; } public String getReceiverName() { return receiverName; } public void setReceiverName(String receiverName) { this.receiverName = receiverName; } public String getReceiverPhone() { return receiverPhone; } public void setReceiverPhone(String receiverPhone) { this.receiverPhone = receiverPhone; } public int getOrderState() { return orderState; } public void setOrderState(int orderState) { this.orderState = orderState; } public Date getOrderTime() { return orderTime; } public void setOrderTime(Date orderTime) { this.orderTime = orderTime; } public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } @Override public String toString() { return "Order [id=" + id + ", money=" + money + ", receiverAddress=" + receiverAddress + ", receiverName=" + receiverName + ", receiverPhone=" + receiverPhone + ", orderState=" + orderState + ", orderTime=" + orderTime + ", userId=" + userId + "]"; }
}
|
public class OrderExt extends Order {
private String username;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
@Override public String toString() { return "OrderExt [getUsername()=" + getUsername() + ", toString()=" + super.toString() + "]"; }
}
|
9.2.3.2 编写Mapper接口
//查询订单信息并关联用户信息 public List<OrderExt> findOrdersAndUser(); |
9.2.3.3 编写Mapper映射文件
<!-- 查询order的sql片段 --> <sql id="select_order"> o.id, o.money, o.ordertime, o.paystate, o.receiverAddress, o.receiverName, o.receiverPhone </sql> <sql id="select_user"> u.username </sql>
<select id="findOrdersAndUser" resultType="orderExt"> select <include refid="select_order"></include> <include refid="select_user"></include> FROM `order` o INNER JOIN USER u ON o.userid = u.id </select> |
9.2.3.4 加载Mapper映射文件
<!-- 加载mapper文件 --> <mappers> <!-- 批量加载Mapper文件,需要Mapper接口文件和Mapper映射文件名称相同且在同一个包下 --> <package name="com.yaorange.easybuy.mapper"/> </mappers> |
9.2.3.5 编写测试程序
@Test public void testFindOrdersAndUser() {
// 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 OrderMapper orderMapper=sqlSession.getMapper(OrderMapper.class);
// 调用Mapper对象的方法 List<OrderExt> orderExts = orderMapper.findOrdersAndUser(); System.out.println(orderExts); // 关闭SqlSession sqlSession.close(); } |
9.2.4 resultMap
9.2.4.1 修改Order实体类
在Order.java 添加User属性
public class Order {
private String id;//订单编号 private double money;//订单金额 private String receiverAddress;//收件人地址 private String receiverName;//收件人姓名 private String receiverPhone;//收件人联系方式 private int orderState;//订单状态 private Date orderTime;//订单时间 // private int userId; private User user; //setter/getter } |
9.2.4.2 编写Mapper接口
//查询订单信息,包括用户的用户和电话号码,采用resultMap public List<Order> findOrdersAndUserResultMap(); |
9.2.4.3 编写Mapper映射文件
<!-- 定义resultMap --> <resultMap type="order" id="orderUserResultMap"> <!-- 配置唯一标识符 --> <id column="id" property="id" /> <result column="money" property="money" /> <result column="receiverAddress" property="receiverAddress" /> <result column="receiverName" property="receiverName" /> <result column="receiverPhone" property="receiverPhone" /> <result column="orderState" property="orderState" /> <result column="orderTime" property="orderTime" /> <!-- 映射一对一的关系 --> <!-- [association标签] 一对一关系 --> <!-- [property]:指定关联对象要映射到order的那个属性上 --> <!-- [javaType]:指定关联对象要映射Java类型 --> <association property="user" javaType="User"> <!-- [id标签] 指定关联对象结果集的唯一标识,很重要,不写不会 报错,但是会影响性能 --> <id property="id" column="userid" /> <result property="username" column="username" /> </association> </resultMap> <select id="findOrdersAndUserResultMap" resultMap="orderUserResultMap"> select <include refid="select_order"></include> , <include refid="select_user"></include> FROM `order` o INNER JOIN USER u ON o.userid = u.id </select> |
9.2.4.4 编写测试程序
@Test public void testFindOrdersAndUserResultMap() {
// 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 OrderMapper orderMapper=sqlSession.getMapper(OrderMapper.class);
// 调用Mapper对象的方法 List<Order> orders = orderMapper.findOrdersAndUserResultMap(); System.out.println(orders); // 关闭SqlSession sqlSession.close(); } |
9.2.5 一对一的小结
实现一对一查询
resultType: 使用resultType实现较为简单,如果POJO中没有包含查询出来的列名,需要建了一个扩展类,增加列名对应的属性,即可完成映射。
如果对查询结果没有特殊要求建议使用resultType。
resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊要求,使用resultMap可以完成关联查询映射到POJO的对象属性中。
resultMap可以实现延迟加载,resultType不能实现延迟加载。
9.3 一对多查询
9.3.1 需求
查询订单信息及订单的明细(订单项)。
9.3.2 SQL语句
确定主表:订单表
确定关联查询表:订单项表。
在一对一查询基础上添加订单项表关联即可
SELECT o.id, o.money, o.orderTime, o.orderState, o.receiverAddress, o.receiverName, o.receiverPhone, o.userid, u.username, oi.id, oi.productid, oi.buynum FROM `order` o INNER JOIN USER u ON o.userid = u.id INNER JOIN order_item oi on o.id=oi.orderid |
9.3.3 分析
使用resultType将上面的查询结果映射到POJO中,订单信息将会重复。
要求:对Order映射不能有重复记录
在Order类中添加List<OrderItem> orderItems属性
最终会将订单信息映射到Order中,订单对应的订单项映射到Order中的orderItems属性中
映射成Order记录数为1条。
这个Order中orderItems属性存储了该订单对应的所有的订单明细(两条)。
9.3.4编写实体类
新建OrderItem类
public class OrderItem {
private int id;//订单项的编号 private Order order;//订单 private String productid;//商品信息 private int buynum;//购买的数量 public int getId() { return id; } public void setId(int id) { this.id = id; } public Order getOrder() { return order; } public void setOrder(Order order) { this.order = order; }
public int getBuynum() { return buynum; } public void setBuynum(int buynum) { this.buynum = buynum; } public String getProductid() { return productid; } public void setProductid(String productid) { this.productid = productid; }
}
|
修改Order类,添加List<OrderItem> orderItems属性
//订单项信息 private List<OrderItem> orderItems; public List<OrderItem> getOrderItems() { return orderItems; } public void setOrderItems(List<OrderItem> orderItems) { this.orderItems = orderItems; } |
9.3.5 编写Mapper接口
//查询订单信息,包括用户信息还要包括订单项(明细) public List<Order> findOrdersAndOrderItemsResultMap(); |
9.3.6 编写Mapper映射文件
<sql id="select_order_item"> oi.id oiid, oi.productid, oi.buynum </sql>
<!-- 定义了ordersAndOrderItemsResultMap --> <!-- [extends]:继承已有的resultMap,值为继承resultMap的唯一标识 --> <resultMap type="order" id="ordersAndOrderItemsResultMap" extends="orderUserResultMap"> <!-- 映射关系一对多 --> <!-- [collection标签]:定义一个一对多的关系 --> <!-- [property]:对应映射的Java属性 --> <!-- [ofType]:该集合属性元素对应的Java类型 --> <collection property="orderItems" ofType="orderItem"> <id column="oiid" property="id" /> <result column="productid" property="productid" /> <result column="buynum" property="buynum" /> </collection> </resultMap> <select id="findOrdersAndOrderItemsResultMap" resultMap="ordersAndOrderItemsResultMap"> select <include refid="select_order"></include> , <include refid="select_user"></include> , <include refid="selct_order_item"></include> FROM `order` o INNER JOIN USER u ON o.userid = u.id INNER JOIN order_item oi on o.id=oi.orderid </select> |
9.3.7 编写测试程序
@Test public void testFindOrdersAndOrderItemsResultMap() {
// 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
// 调用Mapper对象的方法 List<Order> orders = orderMapper.findOrdersAndOrderItemsResultMap(); System.out.println(orders); // 关闭SqlSession sqlSession.close(); } |
9.3.8 一对多的小结
MyBatis使用resultMap的Collection对关联查询的多条记录映射到一个List集合中。
9.4 多对多查询
9.4.1 需求
查询用户信息以及用户购买的商品信息,要求将关联信息映射到主POJO的属性中。
9.4.2 SQL语句
查询主表:User
查询的关联表 order、order_item、prodcut
SELECT o.id, o.money, o.orderTime, o.orderState, o.receiverAddress, o.receiverName, o.receiverPhone, o.userid, u.username, u.email, u.gender, oi.id, oi.productid, oi.buynum, p.`name`, p.img_url FROM USER u INNER JOIN `order` o ON u.id = o.userId INNER JOIN order_item oi ON o.id = oi.orderid INNER JOIN product p ON oi.productid = p.id |
9.4.3 映射的思路
将用户信息映射到User中
在User类中添加订单列表的属性List<Order> orders,将用户创建的订单映射到orders中。
在Order类中添加订单项列表的属性LIst<OrderItem> orderItems,将订单项映射到orderItems中。
在OrderItem类中添加Product属性,将订单项对应的商品映射到product中。
9.4.4 修改是实体类
在User类中添加List<Order> orders 属性
private List<Order> orders;//订单信息 |
在OrderItem类中修改product属性(String productid-> Product product)
private Product product;//商品信息 |
9.4.5 编写Mapper接口
//查询用户及用户购买商品的信息(多对多映射)
//查询用户及用户购买商品的信息(多对多映射) public List<User> findUsersAndProductResultMap(); |
9.4.6 编写Mapper映射文件
<!-- 使用usersAndProdcutResultMap --> <resultMap type="user" id="usersAndProdcutResultMap" extends="userResultMap2"> <!-- 订单信息(一个用户可以有多个订单,一对多) --> <collection property="orders" ofType="order"> <!-- 配置唯一标识符 --> <id column="oid" property="id" /> <result column="money" property="money" /> <result column="receiverAddress" property="receiverAddress" /> <result column="receiverName" property="receiverName" /> <result column="receiverPhone" property="receiverPhone" /> <result column="orderState" property="orderState" /> <result column="orderTime" property="orderTime" /> <!-- 订单项明细(一个订单包含多个订单项) --> <!-- [collection标签]:定义一个一对多的关系 --> <!-- [property]:对应映射的Java属性 --> <!-- [ofType]:该集合属性元素对应的Java类型 --> <collection property="orderItems" ofType="orderItem"> <id column="oiid" property="id" /> <result column="buynum" property="buynum" /> <!-- 商品信息(一个订单项对应一个商品,一对一) --> <association property="product" javaType="product"> <id column="productid" property="id" /> <result column="name" property="name" /> <result column="price" property="price" /> </association> </collection> </collection>
</resultMap>
<select id="findUsersAndProductResultMap" resultMap="usersAndProdcutResultMap"> SELECT o.id oid, o.money, o.orderTime, o.orderState, o.receiverAddress, o.receiverName, o.receiverPhone, u.id, u.username, u.email, u.gender, oi.id oiid, oi.productid, oi.buynum, p.`name`, p.img_url FROM USER u INNER JOIN `order` o ON u.id = o.userId INNER JOIN order_item oi ON o.id = oi.orderid INNER JOIN product p ON oi.productid = p.id
</select> |
9.4.7编程测试程序
@Test public void testFindUsersAndProductResultMap() {
// 创建SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 通过SqlSession对象,获取UserMapper接口的动态代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 调用Mapper对象的方法 List<User> users = userMapper.findUsersAndProductResultMap(); System.out.println(users); // 关闭SqlSession sqlSession.close(); }
|
9.4.8 多对多的小结
将查询用户购买商品信息明细进行查询,(用户名、购买商品的名称、购买商品的时间,购买商品数量)。
一对多是多对多的特例,如下需求:
查询用户购买的商品信息,用户和商品的关系是多对多的关系。
查询字段:用户账号、用户名称、购买商品的数量、商品的明细。
使用resultMap将用户购买商品的信息映射到user对象中
User->order->order_item->product
所以使用resultMap是针对那些对查询结果映射有特殊要求的功能,比如特殊要求映射为List集合。
9.5高级映射的总结
resultType
作用:
将查询结果按照SQL列名与POJO属性名一致映射到POJO中。
resultMap:
使用association和collection完成一对一和一对多的高级映射(对结果有特殊要求)。
association:
作用:
将关联查询信息映射到一个POJO对象中
场景:
为了方便查询关系信息可以使用association将关联信息映射为对象的POJO属性,比如查询订单相应的用户信息。
使用resultType无法将查询结果映射到POJO对象的pojo属性中,根据对结果集查询遍历需要选择使用resultType还是resultMap。
Collection:
作用:将关联查询信息关联到一个List集合中。
场景:
比如查询订单包含订单项(明细),为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中。
如果使用resultType无法将查询结果映射到list集合中。
dsd