-
概述
mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。
采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与jdbc api 打交道,就可以完成对数据库的持久化操作。为了我们能够更好掌握框架运行的内部过程,并且有更好的体验,下面我们将从自定义 Mybatis 框架开始来学习框架。此时我们将会体验框架从无到有的过程体验,也能够很好的综合前面阶段所学的基础。
搭建 Mybatis 开发环境
-
创建 maven 工程并添加 Mybatis3.4.5 的坐标
-
编写User实体类
-
编写持久层接口 IUserDao(可以命名为 UserDao 或者 UserMapper)
public interface IUserDao {
List<User> findAll();
} -
编写持久层接口的映射文件 IUserDao.xml
要求:
-
创造位置必须在resources目录下和持久层接口在相同的包中。
-
名称必须以持久层接口名称命名文件名,扩展名是.xml
-
-
在 IUserDao.xml页面中填写查询语句
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE mapper 3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 5 <mapper namespace="com.itheima.dao.IUserDao"> 6 <!-- 配置查询所有操作 --> 7 <select id="findAll" resultType="com.itheima.domain.User"> 8 select * from user 9 </select>
-
编写 SqlMapConfig.xml 配置文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE configuration 3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 5 <configuration> 6 <!-- 配置 mybatis 的环境 --> 7 <environments default="mysql"> 8 <!-- 配置 mysql 的环境 --> 9 <environment id="mysql"> 10 <!-- 配置事务的类型 --> 11 <transactionManager type="JDBC"></transactionManager> 12 <!-- 配置连接数据库的信息:用的是数据源(连接池) --> 13 <dataSource type="POOLED"> 14 <property name="driver" value="com.mysql.jdbc.Driver"/> 15 <property name="url" value="jdbc:mysql://localhost:3306/ee50"/> 16 <property name="username" value="root"/> 17 <property name="password" value="1234"/> 18 </dataSource> 19 </environment> 20 </environments> 21 <!-- 告知 mybatis 映射配置的位置 --> 22 <mappers> 23 <mapper resource="com/itheima/dao/IUserDao.xml"/> 24 </mappers> 25 </configuration>
-
编写测试类
1 public class MybatisTest { 2 public static void main(String[] args) throws Exception { 3 //1.读取配置文件 4 InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); 5 //2.创建 SqlSessionFactory 的构建者对象 6 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); 7 //3.使用构建者创建工厂对象 SqlSessionFactory 8 SqlSessionFactory factory = builder.build(in); 9 //4.使用 SqlSessionFactory 生产 SqlSession 对象 10 SqlSession session = factory.openSession(); 11 //5.使用 SqlSession 创建 dao 接口的代理对象 12 IUserDao userDao = session.getMapper(IUserDao.class); 13 //6.使用代理对象执行查询所有方法 14 List<User> users = userDao.findAll(); 15 for (User user : users) { 16 System.out.println(user); 17 } 18 //7.释放资源 19 session.close(); 20 in.close(); 21 } 22 }
基于注解的 mybatis 使用
-
修改 SqlMapConfig.xml
<!-- 告知 mybatis 映射配置的位置 -->
<mappers>
<mapper class="com.itheima.dao.IUserDao"/>
</mappers> -
在使用基于注解的 Mybatis 配置时,请移除 xml 的映射配置(IUserDao.xml)。
自定义 Mybatis 框架
前期准备
-
引入工具类XMLConfigBuilder到项目中
-
编写 SqlMapConfig.xml
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <configuration> 3 <environments default="development"> 4 <environment id="development"> 5 <transactionManager type="JDBC"/> 6 <dataSource type="POOLED"> 7 <property name="driver" value="com.mysql.jdbc.Driver"></property> 8 <property name="url" value="jdbc:mysql:///eesy"></property> 9 <property name="username" value="root"></property> 10 <property name="password" value="1234"></property> 11 </dataSource> 12 </environment> 13 </environments> 14 </configuration>
-
编写读取配置文件类
1 public class Resources { 2 /** 3 * 用于加载 xml 文件,并且得到一个流对象 4 * 5 * @param xmlPath 6 * @return 在实际开发中读取配置文件: 7 * 第一:使用类加载器。但是有要求:a 文件不能过大。 b 文件必须在类路径下(classpath) 8 * 第二:使用 ServletContext 的 getRealPath() 9 */ 10 public static InputStream getResourceAsStream(String xmlPath) { 11 return Resources.class.getClassLoader().getResourceAsStream(xmlPath); 12 } 13 }
-
编写 Mapper 类
public class Mapper { private String queryString;//sql private String resultType;//结果类型的全限定类名 ... //并生成get set方法 }
-
编写 Configuration 配置类
public class Configuration { private String username; //用户名 private String password;//密码 private String url;//地址 private String driver;//驱动 //map 集合 Map<唯一标识,Mapper> 用于保存映射文件中的 sql 标识及 sql 语句 private Map<String, Mapper> mappers; ... //并生成get set方法 }
-
编写实体类
基于 XML 的自定义 mybatis 框架
-
编写持久层接口和 IUserDao.xml(如上)
-
此处我们使用的也是 mybatis 的配置文件,所以也要把约束删除了
-
-
编写SqlSessionFactoryBuilder构建者类
public class SqlSessionFactoryBuilder { /** * 根据传入的流,实现对 SqlSessionFactory 的创建 * @param in 它就是 SqlMapConfig.xml 的配置以及里面包含的 IUserDao.xml 的配置 * @return */ public SqlSessionFactory build(InputStream in) { DefaultSqlSessionFactory factory = new DefaultSqlSessionFactory(); //给 factory 中 config 赋值 factory.setConfig(in); return factory; } }
-
编写 SqlSessionFactory 接口和实现类
public interface SqlSessionFactory { SqlSession openSession(); } public class DefaultSqlSessionFactory implements SqlSessionFactory { private InputStream config = null; public void setConfig(InputStream config) { this.config = config; } @Override public SqlSession openSession() { DefaultSqlSession session = new DefaultSqlSession(); //调用工具类解析 xml 文件 XMLConfigBuilder.loadConfiguration(session, config); return session; } }
-
编写 SqlSession 接口和实现类
import javax.security.auth.login.Configuration; import java.lang.reflect.Proxy; import java.sql.SQLException; import java.util.List; import java.util.concurrent.Executor; public interface SqlSession { //创建 Dao 接口的代理对象 <T> T getMapper(Class<T> daoClass); //释放资源 void close(); } public class DefaultSqlSession implements SqlSession { //核心配置对象 private Configuration cfg; public void setCfg(Configuration cfg) { this.cfg = cfg; } //连接对象 private Connection conn; //调用 DataSourceUtils 工具类获取连接 public Connection getConn() { try { conn = DataSourceUtil.getDataSource(cfg).getConnection(); return conn; } catch (Exception e) { throw new RuntimeException(e); } } /** * 动态代理: * 涉及的类:Proxy * 使用的方法:newProxyInstance * 方法的参数: * ClassLoader:和被代理对象使用相同的类加载器,通常都是固定的 * Class[]:代理对象和被代理对象要求有相同的行为。(具有相同的方法) * InvocationHandler:如何代理。需要我们自己提供的增强部分的代码 */ @Override public <T> T getMapper(Class<T> daoClass) { conn = getConn(); System.out.println(conn); T daoProxy = (T) Proxy.newProxyInstance(daoClass.getClassLoader(), new Class[]{daoClass}, new MapperProxyFactory(cfg.getMappers(), conn)); return daoProxy; }//释放资源 @Override public void close() { try { System.out.println(conn); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } //查询所有方法 public <E> List<E> selectList(String statement) { Mapper mapper = cfg.getMappers().get(statement); return new Executor().selectList(mapper, conn); } }
-
编写用于创建 Dao 接口代理对象的类
public class MapperProxyFactory implements InvocationHandler { private Map<String, Mapper> mappers; private Connection conn; public MapperProxyFactory(Map<String, Mapper> mappers, Connection conn) { this.mappers = mappers; this.conn = conn; } /** * 对当前正在执行的方法进行增强 * 取出当前执行的方法名称 * 取出当前执行的方法所在类 * 拼接成 key * 去 Map 中获取 Value(Mapper) * 使用工具类 Executor 的 selectList 方法 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //1.取出方法名 String methodName = method.getName(); //2.取出方法所在类名 String className = method.getDeclaringClass().getName(); //3.拼接成 Key String key = className + "." + methodName; //4.使用 key 取出 mapper Mapper mapper = mappers.get(key); if (mapper == null) { throw new IllegalArgumentException("传入的参数有误,无法获取执行的必要条件"); } //5.创建 Executor 对象 Executor executor = new Executor(); return executor.selectList(mapper, conn); } }
-
运行测试类
public class MybatisTest { public static void main(String[] args) throws Exception { //1.读取配置文件 InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建 SqlSessionFactory 的构建者对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3.使用构建者创建工厂对象 SqlSessionFactory SqlSessionFactory factory = builder.build(in); //4.使用 SqlSessionFactory 生产 SqlSession 对象 SqlSession session = factory.openSession(); //5.使用 SqlSession 创建 dao 接口的代理对象 IUserDao userDao = session.getMapper(IUserDao.class); //6.使用代理对象执行查询所有方法 List<User> users = userDao.findAll(); for (User user : users) { System.out.println(user); } //7.释放资源 session.close(); in.close(); } }
基于注解方式定义 Mybatis 框架
-
自定义@Select 注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
String value();
} -
修改持久层接口
public interface IUserDao {
@Select("select * from user")
List<User> findAll();
} -
修改 SqlMapConfig.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <configuration> 3 <!-- 配置 mybatis 的环境 --> 4 <environments default="mysql"> 5 <!-- 配置 mysql 的环境 --> 6 <environment id="mysql"> 7 <!-- 配置事务的类型 --> 8 <transactionManager type="JDBC"></transactionManager> 9 <!-- 配置连接数据库的信息:用的是数据源(连接池) --> 10 <dataSource type="POOLED"> 11 <property name="driver" value="com.mysql.jdbc.Driver"/> 12 <property name="url" value="jdbc:mysql://localhost:3306/ee50"/> 13 <property name="username" value="root"/> 14 <property name="password" value="1234"/> 15 </dataSource> 16 </environment> 17 </environments> 18 <!-- 告知 mybatis 映射配置的位置 --> 19 <mappers> 20 <mapper class="com.itheima.dao.IUserDao"/> 21 </mappers> 22 </configuration>
自定义 Mybatis 的设计模式说明
-
工厂模式(SqlSessionFactory)
工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式。著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在Java程序系统可以说是随处可见。因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑使用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。
-
代理模式(MapperProxyFactory)
-
组成:
-
抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
-
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
-
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
-
-
代理模式分为静态和动态代理
-
-
构建者模式(SqlSessionFactoryBuilder)
创建者模式是java23种设计模式之一,英文叫Builder Pattern。其核心思想是将一个“复杂对象的构建算法”与它的“部件及组装方式”分离,使得构件算法和组装方式可以独立应对变化;复用同样的构建算法可以创建不同的表示,不同的构建过程可以复用相同的部件组装方式。
总结
基于代理 Dao 实现 CRUD 操作
-
使用要求:
-
持久层接口和持久层接口的映射配置必须在相同的包下
-
持久层映射配置中 mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名
-
SQL 语句的配置标签<select>,<insert>,<delete>,<update>的 id 属性必须和持久层接口的方法名相同
-
-
配置文件的细节
-
resultType 属性:用于指定结果集的类型。
-
parameterType 属性:用于指定传入参数的类型。
-
sql 语句中使用#{}字符: 它代表占位符,都是用于执行语句时替换实际的数据,具体的数据是由#{}里面的内容决定的。
-
#{}中内容的写法:由于数据类型是基本类型,所以此处可以随意写。
-
它用的是 ognl 表达式。
-
它是 apache 提供的一种表达式语言,全称是:Object Graphic Navigation Language 对象图导航语言
-
-
-
在测试类添加测试(并把公共部分提取出来)
public class MybastisCRUDTest { private InputStream in; private SqlSessionFactory factory; private SqlSession session; private IUserDao userDao; @Test public void testFindOne() { //6.执行操作 User user = userDao.findById(41); System.out.println(user); } @Before//在测试方法执行之前执行 public void init() throws Exception { //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建构建者对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //3.创建 SqlSession 工厂对象 factory = builder.build(in); //4.创建 SqlSession 对象 session = factory.openSession(); //5.创建 Dao 的代理对象 userDao = session.getMapper(IUserDao.class); } @After//在测试方法执行完成之后执行 public void destroy() throws Exception { //实现事务提交 session.commit(); //7.释放资源 session.close(); in.close(); } }
问题扩展
-
新增用户 id 的返回值
新增用户后,同时还要返回当前新增用户的 id 值,因为 id 是由数据库的自动增长来实现的,所以就相当于我们要在新增后将自动增长 auto_increment 的值返回。
<insert id="saveUser" parameterType="USER"> <!-- 配置保存时获取插入的 id --> <selectKey keyColumn="id" keyProperty="id" resultType="int"> select last_insert_id(); </selectKey> insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert>
-
模糊查询的三种方式
<!-- 第一种 --> <select id="findByName" resultType="com.itheima.domain.User" parameterType="String"> select * from user where username like #{username} </select> @Test public void testFindByName(){ //5.执行查询一个方法 List<User> users = userDao.findByName("%王%"); for(User user : users){ System.out.println(user); } } //这种方法%要作为参数一起拼接传递进去 <!-- 第二种 --> <select id="findByName" parameterType="string" resultType="com.itheima.domain.User"> select * from user where username like '%${value}%' </select> //在上面将原来的#{}占位符,改成了${value}。注意如果用模糊查询的这种写法,那么${value}的写法就是固定的,不能写成其它名字。 <!-- 第三种 --> <select id="findByName" parameterType="string" resultType="com.itheima.domain.User"> select * from user where username like '%' #{username} '%' </select> //这种方法%和#{}之间要有空格
-
#{}与${}的区别
-
#{}表示一个占位符号,自动进行 java 类型和 jdbc 类型转换,可以有效防止 sql 注入。
-
${}表示拼接 sql 串,e 传入的内容拼接在 sql 中且不进行 jdbc 类型转换,不可以有效防止 sql 注入。
-
-
Mybatis 的参数深入
parameterType 配置参数
resultType 配置结果类型
resultMap 结果类型
当查询的数据库的列名和实体类的属性名称不一致时,我们一般在sql语句上用 as 给每个字段名起上别名 ,让其别名和实体类的属性名称保持一致从而实现封装。resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。
在 select 标签中使用 resultMap 属性指定引用即可。
<!-- type 属性:指定实体类的全限定类名 id 属性:给定一个唯一标识,是给查询 select 标签引用用的。 --> <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> <!-- id 标签:用于指定主键字段 result 标签:用于指定非主键字段 column 属性:用于指定数据库列名 property 属性:用于指定实体类属性名称 --> <!-- 映射resultMap配置 --> <select id="findAll" resultMap="userMap"> select * from user </select>
Mybatis 传统 DAO 层开发(了解)
//持久层 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(); List<User> users = session.selectList("com.itheima.dao.IUserDao.findAll"); session.close(); return users; } @Override public User findById(Integer userId) { SqlSession session = factory.openSession(); User user = session.selectOne("com.itheima.dao.IUserDao.findById", userId); session.close(); return user; } @Override public int saveUser(User user) { SqlSession session = factory.openSession(); int res = session.insert("com.itheima.dao.IUserDao.saveUser", user); session.commit(); session.close(); return res; } @Override public int updateUser(User user) { SqlSession session = factory.openSession(); int res = session.update("com.itheima.dao.IUserDao.updateUser", user); session.commit(); session.close(); return res; } @Override public int deleteUser(Integer userId) { SqlSession session = factory.openSession(); int res = session.delete("com.itheima.dao.IUserDao.deleteUser", userId); session.commit(); session.close(); return res; } @Override public int findTotal() { SqlSession session = factory.openSession(); int res = session.selectOne("com.itheima.dao.IUserDao.findTotal"); session.close(); return res; } }
SqlMapConfig.xml配置文件
配置内容
SqlMapConfig.xml 中配置的内容和顺序
-properties(属性)
--property
-settings(全局配置参数)
--setting
-typeAliases(类型别名)
--typeAliase
--package
-typeHandlers(类型处理器)
-objectFactory(对象工厂)
-plugins(插件)
-environments(环境集合属性对象)
--environment(环境子属性对象)
---transactionManager(事务管理)
---dataSource(数据源)
-mappers(映射器)
--mapper
--package
properties(属性)
在使用 properties 标签配置时,我们可以采用两种方式指定属性配置。
第一种
<properties>
<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="jdbc.url" value="jdbc:mysql://localhost:3306/eesy"/>
<property name="jdbc.username" value="root"/>
<property name="jdbc.password" value="1234"/>
</properties>
第二种
在 classpath 下定义 db.properties 文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234
properties 标签配置
<!-- 配置连接数据库的信息
resource 属性:用于指定 properties 配置文件的位置,要求配置文件必须在类路径下
-->
<properties url="file:///D:/IdeaProjects/day02_eesy_01mybatisCRUD/src/main/resources/jdbcConfig.properties"></properties>
此时我们的 dataSource 标签就变成了引用上面的配置
<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>
typeAliases(类型别名)
自定义别名
在 SqlMapConfig.xml 中配置: <typeAliases> <!-- 单个别名定义 --> <typeAlias alias="user" type="com.itheima.domain.User"/> <!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) --> <package name="com.itheima.domain"/> <package name="其它包"/> </typeAliases>
mappers(映射器)
1.使用相对于类路径的资源 <mapper resource="com/itheima/dao/IUserDao.xml" /> 2.使用 mapper 接口类路径 此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。 <mapper class="com.itheima.dao.UserDao"/> 3.注册指定包下的所有 mapper 接口 <package name="cn.itcast.mybatis.mapper"/>
Mybatis 的动态 SQL 语句
1 <!-- 1. 动态 SQL 之<if>标签--> 2 <select id="findByUser" resultType="user" parameterType="user"> 3 select * from user where 1=1 4 <if test="username!=null and username != '' "> 5 and username like #{username} 6 </if> 7 <if test="address != null"> 8 and address like #{address} 9 </if> 10 </select> 11 注意:要注意 where 1=1 的作用~! 12 13 <!-- 2. 动态 SQL 之<where>标签--> 14 <!-- 根据用户信息查询 --> 15 <select id="findByUser" resultType="user" parameterType="user"> 16 <include refid="defaultSql"></include> 17 <where> 18 <if test="username!=null and username != '' "> 19 and username like #{username} 20 </if> 21 <if test="address != null"> 22 and address like #{address} 23 </if> 24 </where> 25 </select> 26 27 <!-- 3. 动态 SQL 之<foreach>标签--> 28 <!-- 查询所有用户在 id 的集合之中 --> 29 <select id="findInIds" resultType="user" parameterType="queryvo"> 30 <!-- select * from user where id in (1,2,3,4,5); --> 31 <include refid="defaultSql"></include> 32 <where> 33 <if test="ids != null and ids.size() > 0"> 34 <foreach collection="ids" open="id in ( " close=")" item="uid" 35 separator=","> 36 #{uid} 37 </foreach> 38 </if> 39 </where> 40 </select> 41 SQL 语句: 42 select 字段 from user where id in (?) 43 <foreach>标签用于遍历集合,它的属性: 44 collection:代表要遍历的集合元素,注意编写时不要写#{} 45 open:代表语句的开始部分 46 close:代表结束部分 47 item:代表遍历集合的每个元素,生成的变量名 48 sperator:代表分隔符 49 50 <!-- 4. Mybatis 中简化编写的 SQL 片段--> 51 <!-- 抽取重复的语句代码片段 --> 52 <sql id="defaultSql"> 53 select * from user 54 </sql> 55
Mybatis 多表查询
一对一(一对多)
-
方式一:如果查询出来的字段包括第一个pojo类(如Account)的所有的属性和第二个pojo类(User)的部分属性的话,可以新建第三个pojo类(AccountUser)使其继承Account类,并在类中定义查询出来的剩余字段,返回值类型 returnType的值设置为 AccountUser 类型,这样就可以接收所有字段了。
-
方式二:使用 resultMap,定义专门的 resultMap 用于映射一对一查询结果。
-
在 Account 类中加入 User 类的对象作为 Account 类的一个属性。
-
重新定义 AccountDao.xml 文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE mapper 3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 5 <mapper namespace="com.itheima.dao.IAccountDao"> 6 <!-- 建立对应关系 --> 7 <resultMap type="account" id="accountMap"> 8 <id column="aid" property="id"/> 9 <result column="uid" property="uid"/> 10 <result column="money" property="money"/> 11 <!-- 它是用于指定从表方的引用实体属性的 --> 12 <association property="user" javaType="user"> 13 <id column="id" property="id"/> 14 <result column="username" property="username"/> 15 <result column="sex" property="sex"/> 16 <result column="birthday" property="birthday"/> 17 <result column="address" property="address"/> 18 </association> 19 </resultMap> 20 <select id="findAll" resultMap="accountMap"> 21 select u.*,a.id as aid,a.uid,a.money from account a,user u where a.uid =u.id; 22 </select> 23 </mapper>
-
一对多
-
在 User 类中加入 List<Account>作为 User 类的一个属性。
-
重新定义 UserDao.xml 文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE mapper 3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 5 <mapper namespace="com.itheima.dao.IUserDao"> 6 <resultMap type="user" id="userMap"> 7 <id column="id" property="id"></id> 8 <result column="username" property="username"/> 9 <result column="address" property="address"/> 10 <result column="sex" property="sex"/> 11 <result column="birthday" property="birthday"/> 12 <!-- collection 是用于建立一对多中集合属性的对应关系 13 ofType 用于指定集合元素的数据类型 14 --> 15 <collection property="accounts" ofType="account"> 16 <id column="aid" property="id"/> 17 <result column="uid" property="uid"/> 18 <result column="money" property="money"/> 19 </collection> 20 </resultMap> 21 <!-- 配置查询所有操作 --> 22 <select id="findAll" resultMap="userMap"> 23 select u.*,a.id as aid ,a.uid,a.money from user u left outer join account a on u.id =a.uid 24 </select> 25 </mapper> 26 collection:部分定义了用户关联的账户信息。表示关联查询结果集 27 property="accList":关联查询的结果集存储在 User 对象的上哪个属性。 28 ofType="account":指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名。
多对多
Mybatis 延迟加载策略
何为延迟加载?
延迟加载:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
坏处:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
使用 assocation 实现延迟加载
账号持久层和映射文件
public interface IAccountDao {
//查询所有账户,同时获取账户的所属用户名称以及它的地址信息
List<Account> findAll();
}
<mapper namespace="com.itheima.dao.IAccountDao">
<!-- 建立对应关系 -->
<resultMap type="account" id="accountMap">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!-- 它是用于指定从表方的引用实体属性的 -->
<!-- select: 填写我们要调用的 select 映射的 id -->
<!-- column : 填写我们要传递给 select 映射的参数 -->
<association property="user" javaType="user"
select="com.itheima.dao.IUserDao.findById"
column="uid">
</association>
</resultMap>
<select id="findAll" resultMap="accountMap">
select * from account
</select>
</mapper>
用户持久层和映射文件
public interface IUserDao {
//根据 id 查询
User findById(Integer userId);
}
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int">
select * from user where id = #{uid}
</select>
</mapper>
要在 Mybatis 的配置文件 SqlMapConfig.xml 文件中添加延迟加载的配置
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
使用 Collection 实现延迟加载
与使用 assocation 实现延迟加载,都是在标签内添加select和column属性使其进行映射
像大多数的持久化框架一样,Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。Mybatis 中缓存分为一级缓存,二级缓存
Mybatis 一级缓存
一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
Mybatis 二级缓存
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
二级缓存的开启与关闭
第一步:在 SqlMapConfig.xml 文件开启二级缓存
<settings>
<!-- 开启二级缓存的支持 -->
<setting name="cacheEnabled" value="true"/>
</settings>
因为 cacheEnabled 的取值默认就为 true,所以这一步可以省略不配置。为 true 代表开启二级缓存;为
false 代表不开启二级缓存。
第二步:配置相关的 Mapper 映射文件
<cache>标签表示当前这个 mapper 映射将使用二级缓存,区分的标准就看 mapper 的 namespace 值。
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 开启二级缓存的支持 -->
<cache></cache>
</mapper>
第三步:配置 statement 上面的 useCache 属性
<!-- 根据 id 查询 -->
<select id="findById" resultType="user" parameterType="int" useCache="true">
select * from user where id = #{uid}
</select>
将 UserDao.xml 映射文件中的<select>标签中设置 useCache=”true”代表当前这个 statement 要使用二级缓存,如果不使用二级缓存可以设置为 false。
注意:针对每次查询都需要最新的数据 sql,要设置成 useCache=false,禁用二级缓存。
Mybatis 注解开发
mybatis 的常用注解说明
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用
使用注解方式开发持久层接口
public interface IUserDao { //查询所有用户 @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(); //根据 id查询一个用户 @Select("select * from user where id = #{uid} ") @ResultMap("userMap") User findById(Integer userId); //保存操作 @Insert("insert into user(username, sex, birthday, address)values(# {username},#{sex},#{birthday},#{address})") @SelectKey(keyColumn = "id", keyProperty = "id", resultType = Integer.class, before =false, statement = {"select last_insert_id()"}) int saveUser(User user); //更新操作 @Update("update user set username=#{username}, address =#{address}, sex =#{sex}, birthday =#{birthday}where id =#{id}") int updateUser(User user); //删除用户 @Delete("delete from user where id = #{uid} ") int deleteUser(Integer userId); //查询使用聚合函数 @Select("select count(*) from user ") int findTotal(); //模糊查询 @Select("select * from user where username like #{username} ") List<User> findByName(String name); }
编写 SqlMapConfig 配置文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE configuration 3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 5 <configuration> 6 <!-- 配置 properties 文件的位置 --> 7 <properties resource="jdbcConfig.properties"></properties> 8 <!-- 配置别名的注册 --> 9 <typeAliases> 10 <package name="com.itheima.domain"/> 11 </typeAliases> 12 <!-- 配置环境 --> 13 <environments default="mysql"> 14 <!-- 配置 mysql 的环境 --> 15 <environment id="mysql"> 16 <!-- 配置事务的类型是 JDBC --> 17 <transactionManager type="JDBC"></transactionManager> 18 <!-- 配置数据源 --> 19 <dataSource type="POOLED"> 20 <property name="driver" value="${jdbc.driver}"/> 21 <property name="url" value="${jdbc.url}"/> 22 <property name="username" value="${jdbc.username}"/> 23 <property name="password" value="${jdbc.password}"/> 24 </dataSource> 25 </environment> 26 </environments> 27 <!-- 配置映射信息 --> 28 <mappers> 29 <!-- 配置 dao 接口的位置,它有两种方式 30 第一种:使用 mapper 标签配置 class 属性 31 第二种:使用 package 标签,直接指定 dao 接口所在的包 32 --> 33 <package name="com.itheima.dao"/> 34 </mappers> 35 </configuration>
使用注解实现复杂关系映射开发
-
一对一、一对多
@One 注解(一对一)
代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@One 注解属性介绍:
select 指定用来多表查询的 sqlmapper
fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。。
使用格式:@Result(column=" ",property="",one=@One(select=""))
@Many 注解(多对一)
代替了<Collection>标签,是多表查询的关键,在注解中用来指定子查询返回对象集合。
注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType (一般为 ArrayList)但是注解中可以不定义;
使用格式: @Result(property="",column="",many=@Many(select="")) -
采用延迟加载的方式查询
1.一对一
@Result(column="uid",
property="user",
one=@One(select="com.itheima.dao.IUserDao.findById",
fetchType=FetchType.LAZY) )
2.一对多
@Result(column="id",
property="accounts",
many=@Many(select="com.itheima.dao.IAccountDao.findByUid",
fetchType=FetchType.LAZY) ) -
在持久层接口中使用注解配置二级缓存
@CacheNamespace(blocking = true)//mybatis 基于注解方式实现配置二级缓存
public interface IUserDao {
}