1、什么是框架?
它是我们软件开发中的一套解决方案,不同的框架解决的是不同的问题。 使用框架的好处: 框架封装了很多的细节,使开发者可以使用极简的方式实现功能。大大提高开发效率。
MyBatis框架概述
mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。 mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。
2、三层架构
表现层: 是用于展示数据的 业务层: 是处理业务需求 持久层: 是和数据库交互的
3、持久层技术解决方案
JDBC技术: Connection PreparedStatement ResultSet Spring的JdbcTemplate: Spring中对jdbc的简单封装 Apache的DBUtils: 它和Spring的JdbcTemplate很像,也是对Jdbc的简单封装
以上这些都不是框架 JDBC是规范 Spring的JdbcTemplate和Apache的DBUtils都只是工具类
jdbc问题分析
1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
2、Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
3、使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
4、对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。
4、mybatis的概述
mybatis是一个持久层框架,用java编写的。 它封装了jdbc操作的很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动,创建连接等繁杂过程 它使用了ORM思想实现了结果集的封装。 ORM: Object Relational Mappging 对象关系映射 简单的说: 就是把数据库表和实体类及实体类的属性对应起来 让我们可以操作实体类就实现操作数据库表。 user User id userId user_name userName 今天我们需要做到 实体类中的属性和数据库表的字段名称保持一致。 user User id id user_name user_name5、mybatis的入门
mybatis的环境搭建 第一步:创建maven工程并 在pom.xml文件中添加坐标 第二步:创建实体类和dao的接口 第三步:创建Mybatis的主配置文SqlMapConifg.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"> <!--mybatis主配置文件--> <configuration> <!--配置环境--> <environments default="mysql"> <!--配置mysql的环境--> <environment id="mysql"> <!--配置事务的类型--> <transactionManager type="JDBC"></transactionManager> <!--配置数据源(连接池)--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/ssmtest"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!--制定配置文件的位置,映射配置文件指的是每个dao独立的配置文件--> <mappers> <mapper resource="com/atheima/dao/IUserDao.xml"/> </mappers> </configuration> 第四步:创建映射配置文
IUserDao.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.atheima.dao.IUserDao"> <select id="findAll" resultType="com.atheima.domain.User"> select * from user; </select> </mapper> 环境搭建的注意事项:
第一个:创建IUserDao.xml 和 IUserDao.java时名称是为了和我们之前的知识保持一致。 在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper 所以:IUserDao 和 IUserMapper是一样的 第二个:在idea中创建目录的时候,它和包是不一样的 包在创建时:com.itheima.dao它是三级结构 目录在创建时:com.itheima.dao是一级目录 第三个:mybatis的映射配置文件位置必须和dao接口的包结构相同 第四个:映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名 第五个:映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名
当我们遵从了第三,四,五点之后,我们在开发中就无须再写dao的实现类。 mybatis的入门案例 第一步:读取配置文件 第二步:创建SqlSessionFactory工厂 第三步:创建SqlSession 第四步:创建Dao接口的代理对象 第五步:执行dao中的方法 第六步:释放资源 public static void main(String[] args)throws Exception { //1.读取配置文件 InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); //3.使用工厂生产SqlSession对象 SqlSession session = factory.openSession(); //4.使用SqlSession创建Dao接口的代理对象 IUserDao userDao = session.getMapper(IUserDao.class); //5.使用代理对象执行方法 List<User> users = userDao.findAll(); for(User user : users){ System.out.println(user); } //6.释放资源 session.close(); in.close(); } 注意事项: 不要忘记在映射配置中告知mybatis要封装到哪个实体类中 配置的方式:指定实体类的全限定类 mybatis基于注解的入门案例: 把IUserDao.xml移除,在dao接口的方法上使用@Select注解,并且指定SQL语句 同时需要在SqlMapConfig.xml中的mapper配置时,使用class属性指定dao接口的全限定类名。 明确: 我们在实际开发中,都是越简便越好,所以都是采用不写dao实现类的方式。 不管使用XML还是注解配置。 但是Mybatis它是支持写dao实现类的。6、自定义Mybatis的分析:
mybatis在使用代理dao的方式实现增删改查时做什么事呢? 只有两件事: 第一:创建代理对象 第二:在代理对象中调用selectLis第一天要求
1、回顾mybatis自定义和环境搭建+完善自定义Mybatis的注解开发 2、Mybatis基于代理Dao的CRUD操作 重点内容 3、CRUD中可能遇到的问题:参数的传递以及返回值的封装 4、介绍Mybatis基于传统dao方式的使用(自己编写dao的实现类) 了解的内容 5、mybatis主配置文件中的常用配置 properties标签 typeAliases标签 —解释Integer的写法 mappers标签的子标签:package
OGNL表达式: Object Graphic Navigation Language 对象 图 导航 语言
它是通过对象的取值方法来获取数据。在写法上把get给省略了。 比如:我们获取用户的名称 类中的写法:user.getUsername(); OGNL表达式写法:user.username mybatis中为什么能直接写username,而不用user.呢: 因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名自定义Mybatis开发流程图 几种标签属性的解释
resultType属性:用于指定结果集的类型。
parameterType属性:代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。
sql语句中使用**#{}**字符:它代表占位符,相当于原来jdbc部分所学的?,都是用于执行语句时替换实际的数据。具体的数据是由#{}里面的内容决定的。
#{}中内容的写法:由于我们保存方法的参数是一个User对象,此处要写User对象中的属性名称。它用的是ognl表达式。
ognl表达式:它是apache提供的一种表达式语言,全称是:Object Graphic Navigation Language 对象图导航语言它是按照一定的语法格式来获取数据的。语法格式就是使用#{对象.对象}的方式 #{user.username}它会先去找user对象,然后在user对象中找到username属性,并调用getUsername()方法把值取出来。但是我们在parameterType属性上指定了实体类名称,所以可以省略user.而直接写username。
问题拓展:新增用户id的返回值 模糊查询易忘点 模糊查询的另一种配置方式 #{}与${}的区别
#{}表示一个占位符号
通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。#{}可以接收简单类型值或pojo属性值。如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。${}表示拼接sql串
通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换,${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。模糊查询的${value}源码分析 Mybatis与JDBC编程的比较 1.数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。解决:
在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。2.Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。解决:
将Sql语句配置在XXXXmapper.xml文件中与java代码分离。3.向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数对应。解决:
Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。4.对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。解决:
Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。parameterType配置参数 该属性的取值可以是基本类型,引用类型(例如:String类型),还可以是实体类类型(POJO类)。同时也可以使用实体类的包装类,本章节将介绍如何使用实体类的包装类作为参数传递。 基本类型和String我们可以直接写类型名称,也可以使用包名.类名的方式,例如:java.lang.String。实体类类型,目前我们只能使用全限定类名。究其原因,是mybaits在加载时已经把常用的数据类型注册了别名,从而我们在使用时可以不写包名,而我们的是实体类并没有注册别名,所以必须写全限定类名。
4.1 resultType配置结果类型 resultType属性可以指定结果集的类型,它支持基本类型和实体类类 需要注意的是,它和parameterType一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。例如:我们的实体类此时必须是全限定类 同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。
4.2 实体类属性和数据库表的列名已经不一致时 4.2.1 修改映射配置 4.2.2 定义结果类型resultMap 此时映射配置就要用resultMap而不是resulttype
总体流程
分析编写dao的实现类Mybatis的执行过程
分析代理dao的执行过程
5.1.1 持久层Dao实现类
public class UserDaoImpl implements IUserDao { private SqlSessionFactory factory; public UserDaoImpl(SqlSessionFactory factory){ this.factory = factory; } @Override public List<User> findAll() { SqlSession session= factory.openSession(); //参数就是能获取配置信息的Key就是配置文件中的namespace加上方法的名称 List<User> users = session.selectList("com.itheima.dao.IUserDao.findAll"); session.close(); return users; } @Override public void saveUser(User user) { //1.根据factory获取SqlSession对象 SqlSession session = factory.openSession(); //2.调用方法实现保存 session.insert("com.itheima.dao.IUserDao.saveUser",user); //3.提交事务 session.commit(); //4.释放资源 session.close(); } @Override public void updateUser(User user) { //1.根据factory获取SqlSession对象 SqlSession session = factory.openSession(); //2.调用方法实现更新 session.update("com.itheima.dao.IUserDao.updateUser",user); //3.提交事务 session.commit(); //4.释放资源 session.close(); } @Override public void deleteUser(Integer userId) { //1.根据factory获取SqlSession对象 SqlSession session = factory.openSession(); //2.调用方法实现更新 session.update("com.itheima.dao.IUserDao.deleteUser",userId); //3.提交事务 session.commit(); //4.释放资源 session.close(); } @Override public User findById(Integer userId) { //1.根据factory获取SqlSession对象 SqlSession session = factory.openSession(); //2.调用SqlSession中的方法,实现查询一个 User user = session.selectOne("com.itheima.dao.IUserDao.findById",userId); //3.释放资源 session.close(); return user; } @Override public List<User> findByName(String username) { //1.根据factory获取SqlSession对象 SqlSession session = factory.openSession(); //2.调用SqlSession中的方法,实现查询列表 List<User> users = session.selectList("com.itheima.dao.IUserDao.findByName",username); //3.释放资源 session.close(); return users; } @Override public int findTotal() { //1.根据factory获取SqlSession对象 SqlSession session = factory.openSession(); //2.调用SqlSession中的方法,实现查询一个 Integer count = session.selectOne("com.itheima.dao.IUserDao.findTotal"); //3.释放资源 session.close(); return count; } }5.1.2 持久层映射配置
```xml ```java <?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.itheima.dao.IUserDao"> <!--第二种方式--> <!-- <resultMap type="com.itheima.domain.User" id="userMap">--> <!-- <id column="id" property="userId"/>--> <!-- <result column="username" property="userName"/>--> <!-- <result column="sex" property="userSex"/>--> <!-- <result column="address" property="userAddress"/>--> <!-- <result column="birthday" property="userBirthday"/>--> <!-- </resultMap>--> <!--查询所有的操作--> <select id="findAll" resultType="com.itheima.domain.User"> <!--当实体类类型和数据库字段类型不一致时 1.用别名方法解决--> <!--select id as userId,username as userName,address as userAddress,sex as userSex,birthday as userBirthday from user;--> <!--第二种方法自定义字段对应 resultMap参数就对应上面的resultMap的id属性"userMap"--> select * from user; </select> <!--保存用户--> <insert id="saveUser" parameterType="com.itheima.domain.User"> <!--配置插入操作后,获取插入数据的id--> <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER"> select last_insert_id(); </selectKey> insert into user(username,address,sex,birthday)values (#{username},#{address},#{sex},#{birthday}); </insert> <!--更新用户--> <update id="updateUser" parameterType="com.itheima.domain.User"> update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}; </update> <!--删除用户--> <delete id="deleteUser" parameterType="java.lang.Integer"> delete from user where id=#{id}; </delete> <!--查找单个用户--> <select id="findById" parameterType="java.lang.Integer" resultType="com.itheima.domain.User"> select * from user where id=#{id}; </select> <!--根据名字模糊查询用户--> <select id="findByName" parameterType="string" resultType="com.itheima.domain.User"> <!--第二种写法select * from user where username like '%${value}%' 此时传入的参数不需要写%%--> select * from user where username like #{name}; </select> <select id="findTotal" resultType="int"> select count(id) from user; </select> </mapper>Statement和PrepareStatement区别
5.1.3 测试
public class MybaitsTest { private InputStream in; private IUserDao userDao; @Before //用于在测试方法执行之前执行 public void init() throws Exception{ //读取配置资源,生成字节输入流 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //获取SqlSessionFactory SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //使用工厂对象创建dao对象 userDao = new UserDaoImpl(factory); } @After //用于在测试方法执行之后执行 public void destory() throws Exception{ //释放资源 in.close(); } /** * 测试查询所有 */ @Test public void testFindAll(){ //5.执行查询所有方法 List<User> users = userDao.findAll(); for(User user : users){ System.out.println(user); } }6.1.1SqlMapConfig.xml中配置的内容和顺序 6.2properties(属性)
在使用properties标签配置时,我们可以采用两种方式指定属性配置。
<?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属性: 用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下。 --> <properties resource="jdbcConfig.properties"> <!-- <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/ssmtest?serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> --> </properties> <!--配置环境--> <environments default="mysql"> <!--配置ysql的环境--> <environment id="mysql"> <!--配置事务--> <transactionManager type="JDBC"></transactionManager> <!--配置连接池--> <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/itheima/dao/IuserDao.xml"></mapper> </mappers> </configuration>6.3.1自定义别名: 6.4mappers(映射器)
第二天要求
1、mybatis中的连接池以及事务控制 原理部分了解,应用部分会用 mybatis中连接池使用及分析 mybatis事务控制的分析 2、mybatis基于XML配置的动态SQL语句使用 会用即可 mappers配置文件中的几个标签: 3、mybatis中的多表操作 掌握应用 一对多 一对一(?) 多对多 1、连接池: 我们在实际开发中都会使用连接池。 因为它可以减少我们获取连接所消耗的时间。 2、mybatis中的连接池 mybatis连接池提供了3种方式的配置: 配置的位置: 主配置文件SqlMapConfig.xml中的dataSource标签,type属性就是表示采用何种连接池方式。 type属性的取值: POOLED 采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现 UNPOOLED 采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。 JNDI 采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样。 注意:如果不是web或者maven的war工程,是不能使用的。 我们课程中使用的是tomcat服务器,采用连接池就是dbcp连接池。 3、mybatis中的事务 什么是事务 事务的四大特性ACID 不考虑隔离性会产生的3个问题 解决办法:四种隔离级别
它是通过sqlsession对象的commit方法和rollback方法实现事务的提交和回滚4、mybatis中的多表查询 特例: 如果拿出每一个订单,他都只能属于一个用户。 所以Mybatis就把多对一看成了一对一。
mybatis中的多表查询: 示例:用户和账户 一个用户可以有多个账户 一个账户只能属于一个用户(多个账户也可以属于同一个用户) 步骤: 1、建立两张表:用户表,账户表 让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加 2、建立两个实体类:用户实体类和账户实体类 让用户和账户的实体类能体现出来一对多的关系 3、建立两个配置文件 用户的配置文件 账户的配置文件 4、实现配置: 当我们查询用户时,可以同时得到用户下所包含的账户信息 当我们查询账户时,可以同时得到账户的所属用户信息 步骤: 1、建立两张表:用户表,角色表 让用户表和角色表具有多对多的关系。需要使用中间表,中间表中包含各自的主键,在中间表中是外键。 2、建立两个实体类:用户实体类和角色实体类 让用户和角色的实体类能体现出来多对多的关系 各自包含对方一个集合引用 3、建立两个配置文件 用户的配置文件 角色的配置文件 4、实现配置: 当我们查询用户时,可以同时得到用户所包含的角色信息 当我们查询角色时,可以同时得到角色的所赋予的用户信息连接池说明
1.1Mybatis的连接池技术
我们在前面的WEB课程中也学习过类似的连接池技术,而在Mybatis中也有连接池技术,但是它采用的是自己的连接池技术。在Mybatis的SqlMapConfig.xml配置文件中,通过<dataSource type=”pooled”>来实现Mybatis中连接池的配置。1.1.1Mybatis连接池的分类 Mybatis_POOLED的过程
POOLED和UNPOOLED的对比
1.1.2Mybatis中数据源的配置
<!--配置环境--> <environments default="mysql"> <!--配置ysql的环境--> <environment id="mysql"> <!--配置事务--> <transactionManager type="JDBC"></transactionManager> <!--配置连接池--> <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> <package name="com.itheima.dao"/> </mappers>1.1.3 Mybatis中连接的获取过程分析
1.2 Mybatis的事务控制
1.2.2Mybatis中事务提交方式
Mybatis中事务的提交方式,本质上就是调用JDBC的setAutoCommit()来实现事务控制。
@Before //用于在测试方法执行之前执行 public void init() throws Exception{ //读取配置资源,生成字节输入流 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //获取SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //获取SqlSession对象 sqlSession = factory.openSession(); //获取dao的代理对象 userDao = sqlSession.getMapper(IUserDao.class); } @After //用于在测试方法执行之后执行 public void destory() throws Exception{ //保存事务 如果不保存事务 方法不会报错 但是不会真正执行成功 会导致事务回滚1--Setting autocommit to false--2-- Rolling back JDBC Connection sqlSession.commit(); sqlSession.close(); in.close() } /** * 查询所有用户 * @throws Exception */ @Test public void testFindAll() throws Exception{ //执行查询所有方法 List<User> users = userDao.findAll(); for(User user: users){ System.out.println(user); } } /** * 测试保存用户 */ @Test public void testSaveUser() throws Exception{ User user = new User(); user.setUsername("Coner007"); user.setAddress("五邑大学00"); user.setSex("男"); user.setBirthday(new Date()); System.out.println("保存操作之前: " + user); //执行保存方法 userDao.saveUser(user); System.out.println("保存操作之后: " + user); //配置文件中配置了返回保存用户的id 所以保存后能获取到保存用户的id值 }观察控制台输出 这是我们的Connection的整个变化过程,通过分析我们能够发现之前的CUD操作过程中,我们都要手动进行事务的提交,原因是setAutoCommit()方法,在执行时它的值被设置为false了,所以我们在CUD操作中,必须通过sqlSession.commit()方法来执行提交操作。
1.2.3Mybatis自动提交事务的设置
通过上面的研究和分析,现在我们一起思考,为什么CUD过程中必须使用sqlSession.commit()提交事务?主要原因就是在连接池中取出的连接,都会将调用connection.setAutoCommit(false)方法,这样我们就必须使用sqlSession.commit()方法,相当于使用了JDBC中的connection.commit()方法实现事务提交。 @Before //用于在测试方法执行之前执行 public void init() throws Exception{ //读取配置资源,生成字节输入流 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //获取SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //获取SqlSession对象 sqlSession = factory.openSession(true); //获取dao的代理对象 userDao = sqlSession.getMapper(IUserDao.class); } @After //用于在测试方法执行之后执行 public void destory() throws Exception{ //保存事务 如果不保存事务 方法不会报错 但是不会真正执行成功 会导致事务回滚1--Setting autocommit to false--2-- Rolling back JDBC Connection // sqlSession.commit(); sqlSession.close(); in.close(); }控制台输出 我们发现,此时事务就设置为自动提交了,同样可以实现CUD操作时记录的保存。虽然这也是一种方式,但就编程而言,设置为自动提交方式为false再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务情况来决定提交是否进行提交。
动态SQL标签
我们根据实体类的不同取值,使用不同的SQL语句来进行查询。比如在id如果不为空时可以根据id查询,如果username不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到 <!-- 根据queryVo的条件查询用户 --> <select id="findUserByVo" parameterType="com.itheima.domain.QueryVo" resultMap="userMap"> select * from user where username like #{user.userName} </select> <!-- 根据条件查询 <select id="findUserByCondition" resultMap="userMap" parameterType="user"> select * from user where 1=1 <if test="userName != null"> and username = #{userName} </if> <if test="userSex != null"> and sex = #{userSex} </if> </select>--> <select id="findUserByCondition" resultMap="userMap" parameterType="user"> select * from user <where> <if test="userName != null"> and username = #{userName} </if> <if test="userSex != null"> and sex = #{userSex} </if> </where> </select> <!-- 根据queryvo中的Id集合实现查询用户列表 --> <select id="findUserInIds" resultMap="userMap" parameterType="queryvo"> <include refid="defaultUser"></include> <where> <if test="ids != null and ids.size()>0"> <foreach collection="ids" open="and id in (" close=")" item="uid" separator=","> #{uid} </foreach> </if> </where> </select>Mybatis中简化编写的SQL片段
Sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的。 <!-- 了解的内容:抽取重复的sql语句--> <sql id="defaultUser"> select * from user </sql> <!-- 查询所有 --> <select id="findAll" resultMap="userMap"> <include refid="defaultUser"></include> </select>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.itheima.dao.IAccountDao"> <!--使用resultMap,定义专门的resultMap用于映射一对一查询结果。通过面向对象的(has a)关系可以得知,我们可以在Account类中加入一个User类的对象来代表这个账户是哪个用户的--> <!--第二种方式--> <!-- 定义封装account和user的resultMap --> <resultMap id="accountUserMap" type="account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> <!-- 一对一的关系映射:配置封装user的内容--> <association property="user" column="uid" javaType="user"> <id property="id" column="id"></id> <result column="username" property="username"></result> <result column="address" property="address"></result> <result column="sex" property="sex"></result> <result column="birthday" property="birthday"></result> </association> </resultMap> <!--使用resultMap,定义专门的resultMap用于映射一对一查询结果。通过面向对象的(has a)关系可以得知,我们可以在Account类中加入一个User类的对象来代表这个账户是哪个用户的--> <!-- 查询所有 --> <select id="findAll" resultMap="accountUserMap"> select u.*,a.id as aid,a.uid,a.money from account a , user u where u.id = a.uid; </select> <!--第一种方式 定义AccountUser类--> <!--查询所有账户同时包含用户名和地址信息--> <select id="findAllAccount" resultType="accountuser"> select a.*,u.username,u.address from account a , user u where u.id = a.uid; </select> <!--注意:因为上面查询的结果中包含了账户信息同时还包含了用户信息,所以我们的返回值类型returnType的值设置为AccountUser类型,这样就可以接收账户信息和用户信息了。--> </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="com.itheima.dao.IUserDao"> <!-- 定义User的resultMap--> <resultMap id="userAccountMap" type="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="address" column="address"></result> <result property="sex" column="sex"></result> <result property="birthday" column="birthday"></result> <!-- 配置user对象中accounts集合的映射 --> <collection property="accounts" ofType="account"> <id column="id" property="id"></id> <result column="uid" property="uid"></result> <result column="money" property="money"></result> </collection> </resultMap> <!-- 查询所有 --> <select id="findAll" resultMap="userAccountMap"> select * from user u left outer join account a on u.id = a.uid </select> <!-- 根据id查询用户 --> <select id="findById" parameterType="INT" resultType="user"> select * from user where id = #{uid} </select> </mapper>collection 部分定义了用户关联的账户信息。表示关联查询结果集 property=“accList”: 关联查询的结果集存储在User对象的上哪个属性。 ofType=“account”: 指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名。
结构
public class User implements Serializable { private Integer id; private String username; private String address; private String sex; private Date birthday; //多对多的关系映射,一个用户对应多个角色 private List<Role> roles; public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", address='" + address + '\'' + ", sex='" + sex + '\'' + ", birthday=" + birthday + '}'; } } public class Role implements Serializable { private Integer roleId; private String roleName; private String roleDesc; //多对多的关系映射:一个角色可以赋予多个用户 private List<User> users; public List<User> getUsers() { return users; } public void setUsers(List<User> users) { this.users = users; } public Integer getRoleId() { return roleId; } public void setRoleId(Integer roleId) { this.roleId = roleId; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getRoleDesc() { return roleDesc; } public void setRoleDesc(String roleDesc) { this.roleDesc = roleDesc; } @Override public String toString() { return "Role{" + "roleId=" + roleId + ", roleName='" + roleName + '\'' + ", roleDesc='" + roleDesc + '\'' + '}'; } }IRoleDao.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.itheima.dao.IRoleDao"> <resultMap id="roleMap" type="role"> <id property="roleId" column="rid"></id> <result property="roleName" column="role_name"></result> <result property="roleDesc" column="role_desc"></result> <collection property="users" ofType="user"> <id column="id" property="id"></id> <result column="username" property="username"></result> <result column="address" property="address"></result> <result column="sex" property="sex"></result> <result column="birthday" property="birthday"></result> </collection> </resultMap> <!--查询所有--> <select id="findAll" resultMap="roleMap"> SELECT u.*,r.id as rid,r.role_name,r.role_desc from role r left outer join user_role ur on r.id= ur.rid left outer join user u on u.id = ur.uid </select> </mapper>IUserDao.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.itheima.dao.IUserDao"> <!-- 定义User的resultMap--> <resultMap id="userMap" type="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="address" column="address"></result> <result property="sex" column="sex"></result> <result property="birthday" column="birthday"></result> <collection property="roles" ofType="role"> <id column="roleId" property="id"></id> <result property="roleName" column="role_name"></result> <result property="roleDesc" column="role_desc"></result> </collection> </resultMap> <!-- 查询所有 --> <select id="findAll" resultMap="userMap"> SELECT u.*,r.id as rid,r.role_name,r.role_desc from role r right outer join user_role ur on r.id= ur.rid right outer join user u on u.id = ur.uid </select> <!-- 根据id查询用户 --> <select id="findById" parameterType="INT" resultType="user"> select * from user where id = #{uid} </select> </mapper> 从User出发,我们也可以发现一个用户可以具有多个角色,这样用户到角色的关系也还是一对多关系。这样我们就可以认为User与Role的多对多关系,可以被拆解成两个一对多关系来实现。第四天要求
1、Mybatis中的延迟加载 问题:在一对多中,当我们有一个用户,它有100个账户。 在查询用户的时候,要不要把关联的账户查出来? 在查询账户的时候,要不要把关联的用户查出来?
在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询的。 在查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来。 什么是延迟加载 在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载) 什么是立即加载 不管用不用,只要一调用方法,马上发起查询。 在对应的四种表关系中:一对多,多对一,一对一,多对多 一对多,多对多:通常情况下我们都是采用延迟加载。 多对一,一对一:通常情况下我们都是采用立即加载。2、Mybatis中的缓存 什么是缓存 存在于内存中的临时数据。 为什么使用缓存 减少和数据库的交互次数,提高执行效率。 什么样的数据能使用缓存,什么样的数据不能使用 适用于缓存: 经常查询并且不经常改变的。 数据的正确与否对最终结果影响不大的。 不适用于缓存: 经常改变的数据 数据的正确与否对最终结果影响很大的。 例如:商品的库存,银行的汇率,股市的牌价。 Mybatis中的一级缓存和二级缓存 一级缓存: 它指的是Mybatis中SqlSession对象的缓存。缓存的是对象 当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。 该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中 查询是否有,有的话直接拿出来用。 当SqlSession对象消失时,mybatis的一级缓存也就消失了。
二级缓存: 它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。 二级缓存的使用步骤: 第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置) 第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置) 第三步:让当前的操作支持二级缓存(在select标签中配置) 二级缓存存的是数据不是对象 当第二次调用时 会创建一个对象 把数据封装到该对象中 并返回3、Mybatis中的注解开发 环境搭建 单表CRUD操作(代理Dao方式) 多表查询操作 缓存的配置
1.1何为延迟加载
延迟加载: 就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处: 因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
使用assocation实现延迟加载
<?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.itheima.dao.IAccountDao"> <!-- 定义封装account和user的resultMap --> <resultMap id="accountUserMap" type="account"> <id property="id" column="id"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> <!-- 一对一的关系映射:配置封装user的内容--> <association property="user" column="uid" javaType="user" select="com.itheima.dao.IUserDao.findById"> </association> </resultMap> <!-- 根据用户id查询账户信息 --> <select id="findAll" resultMap="accountUserMap"> select * from account </select> <!-- 根据用户id查询账户信息 --> <select id="findAccountByUid" resultType="account"> select * from account where uid = #{id}; </select> </mapper> select:填写我们要调用的select 映射的id column :填写我们要传递给select 映射的参数开启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> <!-- 配置properties--> <properties resource="jdbcConfig.properties"></properties> <!--配置参数--> <settings> <!--开启Mybaits支持延迟加载--> <setting name="lazyLoadingEnabled" value="true"/> <!--默认就是false的 可以不用配置--> <setting name="aggressiveLazyLoading" value="false"/> </settings> <!--使用typeAliases配置别名,它只能配置domain中类的别名 --> <typeAliases> <package name="com.itheima.domain"></package> </typeAliases> <!--配置环境--> <environments default="mysql"> <!-- 配置mysql的环境--> <environment id="mysql"> <!-- 配置事务 --> <transactionManager type="JDBC"></transactionManager> <!--配置连接池--> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </dataSource> </environment> </environments> <!-- 配置映射文件的位置 --> <mappers> <package name="com.itheima.dao"></package> </mappers> </configuration>使用Collection实现延迟加载
同样我们也可以在一对多关系配置的<collection>结点中配置延迟加载策略。<collection>结点中也有select属性,column属性。需求:完成加载用户对象时,查询该用户所拥有的账户信息。在User实体类中加入List属性
package com.itheima.domain; import java.io.Serializable; import java.util.Date; import java.util.List; /** * @author 黑马程序员 * @Company http://www.ithiema.com */ public class User implements Serializable { private Integer id; private String username; private String address; private String sex; private Date birthday; //一对多关系映射:主表实体应该包含从表实体的集合引用 private List<Account> accounts; public List<Account> getAccounts() { return accounts; } public void setAccounts(List<Account> accounts) { this.accounts = accounts; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", address='" + address + '\'' + ", sex='" + sex + '\'' + ", birthday=" + birthday + '}'; } }编写用户持久层映射配置
编写账户持久层映射配置
<!-- 根据用户id查询账户信息 --> <select id="findAccountByUid" resultType="account"> select * from account where uid = #{id}; </select> @Test public void testFindAll(){ List<User> users = userDao.findAll(); // for(User user : users){ System.out.println("-----每个用户的信息------"); System.out.println(user); System.out.println(user.getAccounts()); } }此时并没有加载Account账户
证明一级缓存的存在
一级缓存是SqlSession级别的缓存,只要SqlSession没有flush或close,它就存在。 public class UserTest { private InputStream in; private SqlSession sqlSession; private IUserDao userDao; private SqlSessionFactory factory; @Before//用于在测试方法执行之前执行 public void init()throws Exception{ //1.读取配置文件,生成字节输入流 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.获取SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //3.获取SqlSession对象 sqlSession = factory.openSession(true); //4.获取dao的代理对象 userDao = sqlSession.getMapper(IUserDao.class); } @After//用于在测试方法执行之后执行 public void destroy()throws Exception{ //提交事务 // sqlSession.commit(); //6.释放资源 sqlSession.close(); in.close(); } /** * 测试一级缓存 */ @Test public void testFirstLevelCache(){ User user1 = userDao.findById(41); System.out.println(user1); User user2 = userDao.findById(41); System.out.println(user2); System.out.println(user1 == user2); //返回结果为true } 我们可以发现,虽然在上面的代码中我们查询了两次,但最后只执行了一次数据库操作,这就是Mybatis提供给我们的一级缓存在起作用了。因为一级缓存的存在,导致第二次查询id为41的记录时,并没有发出sql语句从数据库中查询数据,而是从一级缓存中查询。一级缓存的分析
一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
/** * 测试一级缓存 */ @Test public void testFirstLevelCache(){ User user1 = userDao.findById(41); System.out.println(user1); sqlSession.close(); //在此获取Sqlsesison对象 sqlSession = factory.openSession(); userDao = sqlSession.getMapper(IUserDao.class); User user2 = userDao.findById(41); System.out.println(user2); System.out.println(user1 == user2); //返回值为false 因为缓存被清空 第二是是重新查询的到的 }Mybatis二级缓存
二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。二级缓存结构图
二级缓存的开启与关闭
第一步:在SqlMapConfig.xml文件开启二级缓存
<settings> <!--开启二级缓存的支持--> <setting name="cacheEnabled" value="true"/> </settings>第二步:配置相关的Mapper映射文件
配置statement上面的useCache属性 二级缓存的测试
package com.itheima.test; import com.itheima.dao.IUserDao; import com.itheima.domain.User; 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 org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.InputStream; /** * @author 黑马程序员 * @Company http://www.ithiema.com */ public class SecondLevelCacheTest { private InputStream in; private SqlSessionFactory factory; @Before//用于在测试方法执行之前执行 public void init()throws Exception{ //1.读取配置文件,生成字节输入流 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.获取SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); } @After//用于在测试方法执行之后执行 public void destroy()throws Exception{ in.close(); } /** * 测试一级缓存 */ @Test public void testFirstLevelCache(){ SqlSession sqlSession1 = factory.openSession(); IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class); User user1 = userDao1.findById(41); System.out.println(user1); //一级缓存消失 sqlSession1.close(); SqlSession sqlSession2 = factory.openSession(); IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class); User user2 = userDao2.findById(41); System.out.println(user2); sqlSession2.close(); System.out.println(user1 == user2); } } 经过上面的测试,我们发现执行了两次查询,并且在执行第一次查询后,我们关闭了一级缓存,再去执行第二次查询时,我们发现并没有对数据库发出sql语句,所以此时的数据就只能是来自于我们所说的二级缓存。Mybatis的常用注解说明 使用Mybatis注解实现基本CRUD
使用注解方式开发持久层接口
package com.itheima.dao; import com.itheima.domain.User; import org.apache.ibatis.annotations.*; import java.util.List; /** * @author Coner007 * @creat 2020-09-03-14:15 * 在mybatis中针对CRUD一共有四个注解 * @Select @Insert @Update @Delete */ public interface IUserDao { /** * 查询所有用户 * @return */ @Select("select * from user") @Results(id="userMap", value= { @Result(id=true,column="id",property="userId"), @Result(column="username",property="userName"), @Result(column="sex",property="userSex"), @Result(column="address",property="userAddress"), @Result(column="birthday",property="userBirthday") }) List<User> findAll(); /** * 保存用户 * @param user */ @Insert("insert into user(username,address,sex,birthday) values (#{username},#{address},#{sex},#{birthday})") void saveUser(User user); /** * 更新用户 * @param user */ @Update("update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}") void updateUser(User user); /** * 删除用户 * @param id */ @Delete("delete from user where id=#{id}") void deleteUser(Integer id); /** * 根据id查找用户 * @param id */ @Select("select * from user where id=#{id}") User findById(Integer id); /** * 根据名字模糊查找用户 * @param username */ @Select("select * from user where username like #{username}") List<User> findByName(String username); /** * 查询总数 * @return */ @Select("select count(*) from user") int findTotal(); }编写SqlMapConfig 配置文件
<?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="jdbcConfig.properties"></properties> <!-- 配置别名 --> <typeAliases> <package name="com.itheima.domain"/> </typeAliases> <!-- 配置环境 --> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"></transactionManager> <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> <!-- 指定带有注解的dao接口所在位置 --> <mappers> <mapper class = "com.itheima.dao.IUserDao"></mapper> </mappers> </configuration>使用注解实现复杂关系映射开发
实现复杂关系映射之前我们可以在映射文件中通过配置<resultMap>来实现,在使用注解开发时我们需要借助@Results注解,@Result注解,@One注解,@Many注解。复杂关系映射的注解说明
mybatis基于注解的二级缓存