笔记主要来源
https://gitee.com/lagouedu/Basic-document/blob/master/document/MyBatis.md
l 经典三层
web表现层(视图+controller)、service层、dao层
分层的好处
代码清晰、解耦、便于维护,出现问题容易定位
分层开发下的常见框架
dao层:hibernate、mybatis
service层:spring框架(对象容器)
web表现层:springmvc框架
[了解]原生JDBC操作数据库分析
package com.mybatis.dao; import com.mybatis.pojo.User; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; public class UserDaoImpl implements UserDao { private String driver = "com.mysql.jdbc.Driver"; private String url = "jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"; private String username = "root"; private String password = "root"; public List<User> queryUserList() throws Exception { List<User> userList = new ArrayList<User>(); Class.forName(driver); /** * 问题一:频繁获取/释放数据库连接,影响数据库和应用性能 * 解决:数据库连接池技术,C3P0,DRUID(阿里巴巴荣誉出品,号称前无古人后无来者世界最强没有之一) */ Connection connection = DriverManager.getConnection(url, username, password); /** * 问题二:sql语句硬编码,后期难以维护 * 解决:若sql语句和java代码分离,比如sql写在配置文件中。Mybatis就是这么干的 */ String sql = "select * from user"; /** * 问题三:sql语句where条件和占位符一一对应,后期难以维护 */ // String sql1 = "select * from user where username=? and id=?"; PreparedStatement preparedStatement = connection.prepareStatement(sql); // preparedStatement.setInt(1,2); ResultSet resultSet = preparedStatement.executeQuery(); User user = null; /** * 问题四:结果集解析麻烦,查询列硬编码 * 期望:如果单条记录直接返回实体对象,如果多条记录返回实体的集合 */ while(resultSet.next()) { user = new User(); user.setId(resultSet.getInt("id")); user.setUsername(resultSet.getString("username")); user.setSex(resultSet.getString("sex")); user.setBirthday(resultSet.getDate("birthday")); user.setAddress(resultSet.getString("address")); userList.add(user); } resultSet.close(); preparedStatement.close(); connection.close(); return userList; } }
Mybatis框架是一个半自动的ORM持久层框架,也可以在Java中实现类似 insert(User)的操作最终操作数据库,但是需要我们自己写Sql语句。Mybatis是目前比较流行的Dao层框架。
自定义Mybatis框架小结
l 框架的开发使用流程
编写源代码—>打jar包—>框架使用者引入jar包—>按照框架的规范进行XML配置—>Java程序中调用框架API实现功能
l 引入框架坐标即可,它所依赖的dom4j和jaxen都不需要我们引入,这是Maven的依赖传递
l 框架是基础知识的整合(本课程中的jdbc、dom4j、xpath、反射、数据库元数据等)
l 框架往往都会用到设计模式(本课程扩展了工厂设计模式和构建者设计模式)
l 框架展示了代码的拆分思想,各司其职,便于维护
[掌握]Mybatis框架快速入门
l Mybatis入门程序
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
n pojo类User.java
package com.mybatis.pojo; import java.util.Date; public class User { private Integer id; private String username; private String sex; private Date birthday; private String address; 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 getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", sex='" + sex + '\'' + ", birthday=" + birthday + ", address='" + address + '\'' + '}'; } }
n 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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?characterEncoding=utf8" /> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments> <mappers> <mapper resource="UserMapper.xml"></mapper> </mappers> </configuration>
n UserMapper.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="test"> <select id="queryUserList" resultType="com.mybatis.pojo.User"> select * from user </select> </mapper>
n 测试程序
@Test public void testMybatis() throws IOException { InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); List<User> list = sqlSession.selectList("test.queryUserList"); if(list != null && list.size() > 0) { for (int i = 0; i < list.size(); i++) { User user = list.get(i); System.out.println(user); } } sqlSession.close(); }
[理解]关于三个对象
l SqlSessionFactoryBuilder: 创建工厂,生命周期短暂即可,类似于一个工具类
l SqlSessionFactory: 应该只有一个工厂对象即可。
l SqlSession: 和数据库通信的会话对象,底层对应connection连接。例如:假如多个线程公用一个sqlsession对象(connection),线程A操作了数据库尚未提交事务,线程B操作了数据库把这个connecttion的事务给提交了,会造成数据安全问题。所以一个线程应该有自己的一个sqlsession对象。
换言之,SqlSession是线程不安全的
[掌握]Mybatis入门级CRUD操作
功能需求:
基于已有数据表user,使用MyBatis实现以下功能:
n 根据用户id查询一个用户
n 根据用户名称模糊查询用户列表
n 添加一个用户
n 根据用户id修改用户名
n 根据用户id删除用户
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> <!--mybatis的jar坐标--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <!--log4j日志组件,要同时把兄弟log4j.properties引入--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>utf-8</encoding> </configuration> </plugin> </plugins> </build>
组件:
POJO
SqlMapConfig.xml
新增业务,修改:UserMapper.xml
关联SqlMapConfig和UserMapper
新增业务,修改:Java程序调用(测试程序)
根据用户id查询用户
l UserMapper.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:命名空间,分类管理的sql的作用 --> <mapper namespace="test"> <!-- 查询使用select标签 parameterType:传入的参数类型 resultType:封装的结果类型 --> <select id="queryUserById" parameterType="Integer" resultType="com.mybatis.pojo.User"> <!-- #{}固定取参语法 #{}取参名称:当parameterType传入简单数据类型的时候,取参名称任意,但是建议见名思意,比如是id就#{id} --> select * from user where id=#{id} </select> </mapper>
l 测试程序
/** * 测试用例(Use_case):根据id查询用户 */ @Test public void testQueryUserById() throws IOException { InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); /* * 调用会话对象的api完成crud * param1:锁定sql的唯一标识符 * param2:传入的参数 * */ User user = sqlSession.selectOne("test.queryUserById",1); System.out.println(user); sqlSession.close(); inputStream.close(); }
根据用户名模糊查询用户列表
l #{}方式取参
n UserMapper.xml
<!--根据用户名模糊查询用户列表--> <!--注意:返回多条记录和返回单条记录,resultType配置是一样的--> <select id="queryUserByUsername" parameterType="String" resultType="com.mybatis.pojo.User"> <!-- #{}底层原理:预编译语句(防止sql注入,性能提高) #{}对字符串参数自动添加单引号对 --> select * from user where username like #{username} </select>
n 测试程序
/** * 测试用例(Use_case):根据用户名模糊查询用户列表 */ @Test public void testQueryUserByUsername() throws IOException { InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); // TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2 List<User> userList = sqlSession.selectList("test.queryUserByUsername","%王%"); if(userList != null && userList.size() > 0) { for (int i = 0; i < userList.size(); i++) { User user = userList.get(i); System.out.println(user); } } sqlSession.close(); inputStream.close(); }
l ${}方式取参
n UserMapper.xml
<!-- ${}:另外一种取参方式 ${}取参名称:当parameterType传入简单数据类型的时候,取参名称必须为value字符串 ${}原理:简单的字符串,传什么参数拼接什么 不会对字符串参数添加单引号对 --> <!-- #和$取参使用原则 能用#不用$(从底层原理角度出发说的,#是预编译语句),特殊情况使用$:当你传入的参数是数据库对象(比如表名动态传入),或者是 order by 的那个字段,都不希望自动添加单引号对,所以使用$ --> <select id="queryUserByUsername$" parameterType="String" resultType="com.mybatis.pojo.User"> select * from user where username like ${value} </select>
n 测试程序
/** - 测试用例(Use_case):根据用户名模糊查询用户列表$取参形式 */ @Test public void testQueryUserByUsername$() throws IOException { InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); // TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2 List<User> userList = sqlSession.selectList("test.queryUserByUsername$","'%王%'"); if(userList != null && userList.size() > 0) { for (int i = 0; i < userList.size(); i++) { User user = userList.get(i); System.out.println(user); } } sqlSession.close(); inputStream.close(); }
小结
查询部分小结:
l selectOne,当结果集有多条记录的时候,抛出异常;
l #{}和${}使用原则
能使用#不用,特殊情况比如传入的参数是数据库对象(表名)
或者order by的字段名时,不希望自动添加单引号对;
l 返回结果集列表的时候,resultType和返回单个对象配置都一样;
实现添加用户(及Mysql自增主键返回)
<!-- #{}和${}取参时,当parameterType为pojo的时候,取参名称为pojo的属性名 --> <insert id="saveUser" parameterType="com.mybatis.pojo.User"> <!-- selectKey用于查询主键数据 order:selectKey中的sql在insert之前还是之后执行 AFTER/BEFORE resultType:查询出的主键的类型 keyProperty:查询出的主键数据回显到pojo的哪个属性 --> <selectKey order="AFTER" resultType="Integer" keyProperty="id"> select last_insert_id() </selectKey> insert into user(username,sex,birthday,address) values(#{username},#{sex},#{birthday},#{address}) </insert>
自增主键返回:其实就是mybaits在执行完insert之后立马查询last_insert_id()
根据用户id修改用户名
<update id="updateUsernameById" parameterType="com.mybatis.pojo.User"> update user set username=#{username} where id=#{id}</update>
根据用户id删除用户
<delete id="deleteUserById" parameterType="Integer"> delete from user where id=#{id}</delete>
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定义输出结果的类型。
[掌握]Mybatis的两种Dao开发方式
使用MyBatis开发DAO的方式实现以下的功能:
n 根据用户id查询一个用户信息
原始Dao开发方式
定义接口(UserDao) 写实现类(UserDaoImpl):在实现类中写crud那套程序
l 定义接口UserDao
package com.mybatis.dao; import com.mybatis.pojo.User; /** * 原始Dao开发方式接口定义 */ public interface UserDao { User queryUserById(Integer id) throws Exception; }
l 写实现类
package com.mybatis.dao; import com.mybatis.pojo.User; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; public class UserDaoImpl implements UserDao { /** * 声明一个SqlSessionFactory变量,用于接收service层调用dao层时传入的工厂对象 * 因为工厂对象全局唯一,对于全局唯一的对象,在分层的架构中,dao层使用时候应该由 * 外部传入 */ private SqlSessionFactory sqlSessionFactory; public UserDaoImpl(SqlSessionFactory sqlSessionFactory) { this.sqlSessionFactory = sqlSessionFactory; } @Override public User queryUserById(Integer id) throws Exception { SqlSession sqlSession = sqlSessionFactory.openSession(); User user = sqlSession.selectOne("test.queryUserById", id); sqlSession.close(); return user; } }
注意:分层其实是对原有代码的重新组织
l 思考
一个项目当中会有很多表,都对应dao接口和实现类,实现类就是做增删改查这些东西
能否不写实现类只定义接口,让框架帮我们完成实现类的逻辑,那么就是Mapper动态代理的开发方式
Mapper动态代理开发方式
l 定义一个Mapper接口,这个接口其实和我们UserDao接口是一样的
Mapper接口中的接口方法被调用时候,最终要找到这个接口方法所使用的sql语句?那如何找到呢?
遵从下面4个规范即可
l 从Mybatis框架中拿到一个代理对象(代理的是这个Mapper接口),通过代理对象调用接口当中的方法完成业务
n 传统dao开发方式中的实现类其实起了一个连接、承上启下的作用,连接了接口和xml映射文件,效果就是调用接口方法时能够找到xml映射文件
n Mapper动态代理开发遵从的规范
u sql映射文件的namespace必须和mapper接口的全限定类名保持一致
u mapper接口的接口方法名必须和xml中的sql语句id保持一致
u mapper接口的接口方法形参类型必须和sql语句的输入参数类型保持一致
u mapper接口的接口方法返回类型必须和sql语句的resultType保持一致
小结
原始Dao和Mapper动态代理开发在企业中都很常见,推荐使用Mapper动态代理开发模式
[掌握]全局配置文件SqlMapConfig.xml的使用说明
注意:子标签是有顺序要求的,因为使用了dtd校验
properties(属性)
引入外部属性文件
<properties resource="db.properties"><property name="jdbc.username" value="root1"/></properties>
typeAliases(类型别名)
Mybatis默认支持的别名
别名 | 映射的类型 |
_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 |
map | Map |
注意:更多默认别名参见mybatis源码中的TypeAliasRegistry类
自定义别名
<typeAliases><package name="com.mybatis.pojo"/></typeAliases>
mappers(映射器)
引入sql语句的xml配置文件,把sql语句加载到内存当中
l <mapper resource=""/> 万能型选手,基础crud、原始dao开发、mapper动态代理开发都可以使用这种方式
l 针对mapper动态代理开发dao层,我们进行一个增加
mapper class属性:执行mapper接口类的全限定类名
前提要求: 1、编译之后 mapper.xml和mapper.class在同一个目录文件夹中
2、两个文件名称相同
<mapper class="com.mybatis.mapper.UserMapper"/><package name="com.mybatis.mapper"/>
[掌握]Mybatis的输入类型和结果类型
parameterType(输入类型)
l 传递简单类型
参考上面
l 传递Pojo对象
参考上面
l 传递Pojo包装对象
n 需求:使用POJO包装对象,根据用户名模糊查询用户列表
地址:http://epub.sipo.gov.cn/gjcx.jsp
resultType(输出类型)
l 输出简单类型
查询user表总记录数
l 输出Pojo对象
参考上面
l 输出Pojo列表
参考上面
resultMap(手动映射)
l 订单表POJO类
package com.mybatis.pojo; import java.util.Date; public class Orders { // 订单id private int id; // 用户id private Integer userId; // 订单号 private String number; // 订单创建时间 private Date createtime; // 备注 private String note; public int getId() { return id; } public void setId(int id) { this.id = id; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } public Date getCreatetime() { return createtime; } public void setCreatetime(Date createtime) { this.createtime = createtime; } public String getNote() { return note; } public void setNote(String note) { this.note = note; } @Override public String toString() { return "Orders{" + "id=" + id + ", userId=" + userId + ", number='" + number + '\'' + ", createtime=" + createtime + ", note='" + note + '\'' + '}'; } }
n OrdersMapper.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.mybatis.mapper.OrdersMapper"> <!-- resultType:自动映射,要求查询列名和pojo属性名保持一致 1 最终封装的对象 2 查询列名和pojo属性名一致 当不一致的时候,要使用手动映射 resultMap:配置手动映射 --> <select id="queryOrdersAllList" resultMap="ordersResultMap"> select * from orders </select> <resultMap id="ordersResultMap" type="orders"> <!--手动配置映射关系--> <!-- 主键字段不一致的时候使用<id>标签配置 普通字段不一致的时候使用<result>标签配置 property严格区分大小写,要和pojo属性名称一致 --> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> </resultMap> </mapper>
工程准备
新建一个Maven类型的module:实现Mybatis第二天的保存用户功能,便于基于该功能的代码程序说明Mybatis数据库连接池和Mybatis事务控制。
[理解]Mybatis连接池和事务控制
Mybatis连接池
l POOLED使用连接池
n 什么时候从连接池获取连接
getMapper的时候还是真正调用api操作数据库的时候?
从连接池获取连接的时机:真正操作数据库调用api的时候,不是getMapper的时候
l 其他
n UNPOOLED:不使用数据库连接池(一般不使用)
n JNDI:(前提你的Mybatis环境必须是Web应用)(了解)
u 什么是JNDI
JNDI:java naming directory interface(java命名目录接口),它是一种该服务发布技术,可以通过JNDI技术把数据源发布成一个服务,那么客户端可以调用这个服务
u 为什么必须是web应用
因为支持JNDI技术的往往是tomcat/weblogic/websphere这些中间件才支持JNDI技术
u 如果在Mybatis当中用,怎么用
参考附录
Mybatis事务控制
type="JDBC":使用Jdbc的事务管理机制,connection的事务
Mybatis默认把Jdbc的事务自动提交给关闭了
注意:考虑提交的数据量较小的时候使用自动提交,如果数据量大影响了操作数据库的性能,那么考虑分批次手动控制事务的提交
type="MANAGED":什么都不做,交给其他框架管理事务
[掌握]Mybatis映射文件的Sql深入(即动态Sql)
更优雅更方便的帮助我们拼接sql语句。
根据用户性别和用户名称查询用户列表
if标签/where标签
sql片段
引用sql片段使用include标签,refid指向sql片段的id,如果共享其他mapper文件当中的sql片段,只需要refid前面加上另外一个mapper映射文件的namespace即可
foreach标签
delete from user where id in(1,2,3) String sql = "delete from user where id in(" for(String s: list) { sql += s + "," } substring sql += ")"
需求:根据多个id来查询用户列表
select * from user where id in(1,2,3)
l 形式一:直接传入list集合给mybatis
l 形式二:直接传入数组给Mybatis
Mybatis底层别名定义类
<!-- foreach直接传入Array数组 --> <select id="queryUserByIdsArray" parameterType="int[]" resultType="user"> select id,username,sex,birthday,address from user <!-- foreach:遍历拼接sql语句 collection:遍历的集合,注意当直接传数组,除了知道是数组外没有别的名字,所以collection固定配置为array open:遍历拼接前干点啥(拼接点什么字符串) close:遍历结束后干点啥(拼接点什么字符串) separator:遍历拼接时候使用的分隔符 item:遍历到的具体元素值,等同于下面for循环中的s for(String s : list) { s } --> <foreach collection="array" open=" where id in(" close=")" separator="," item="item"> #{item} </foreach> </select>
l 形式三:传入一个pojo,pojo中有list或者数组
<!-- foreach直接传入pojo,pojo中封装list或者数组 --><select id="queryUserByIdsQueryVo" parameterType="queryvo" resultType="user"> select id,username,sex,birthday,address from user<!-- foreach:遍历拼接sql语句 collection:遍历的集合,当传入pojo的时候,collection配置为pojo当中集合的属性名 如果不指定具体属性名,那么mybatis是不知道要取pojo哪个属性的 open:遍历拼接前干点啥(拼接点什么字符串) close:遍历结束后干点啥(拼接点什么字符串) separator:遍历拼接时候使用的分隔符 item:遍历到的具体元素值,等同于下面for循环中的s for(String s : list) { s } --> <foreach collection="idsList" open=" where id in(" close=")" separator="," item="item"> #{item} </foreach> </select>
[掌握]Mybatis的多表关联查询
多表:至少2张表
多表关系分析技巧:从一条记录出发,比如分析A表和B表的关系,就看A表的一条记录可以对应B表中几条记录,如果对应一条,从A到B表就是一对一的关系,如果对应多条就是一对多的关系。
多对多其实是双向的一对多
l 一对一
从订单表出发到用户表,一条订单记录只会对应用户表的一条记录,一对一
l 一对多
从用户表出发到订单表,一个用户记录可以对应订单表的多条记录,一对多
(通过主外键,一的一方是主表,多的一方是从表,外键在从表多的那一方)
l 多对多
用户和角色,一个用户可以有多个角色,一个角色也可以对应多个用户
通过中间表表达两个表之间的关系
a表 b表
id field.. id field..
中间表(往往两个字段即可)
aid bid
多表关联的SQL语句表达分析
l Sql语句表达
n 笛卡尔积
u SELECT * FROM USER,orders
SELECT * FROM USER u,orders o WHERE u.id=o.user_id(笛卡尔积进一步筛选)
n 关联查询
u 内关联 innder join on(和笛卡尔积进一步筛选效果一样)
SELECT * FROM USER u INNER JOIN orders o ON u.id=o.user_id
u 外连接(outer join on)(使用较多)
左外连接(left join) left join左边的表是基础表
SELECT * FROM USER u LEFT JOIN orders o ON u.id=o.user_id
右外连接(right join) right join右边的表是基础表
SELECT * FROM USER u RIGHT JOIN orders o ON u.id=o.user_id
l 内关联和外关联分析
一对一查询
Mybatis帮我们做的事情:
1、 SQL语句是用户自己写,Mybatis框架只是帮我们执行而已
2、 封装结果集
需求:查询订单表全部数据,关联查询出订单对应的用户数据(username address)
l Mapper映射文件
<select id="queryOrdersUser" resultMap="ordersUserResultMap"> SELECT o.`id`, o.`user_id`, o.`number`, o.`createtime`, o.`note`, u.`username`, u.`address` FROM orders o LEFT JOIN USER u ON o.`user_id` = u.`id` </select> <resultMap id="ordersUserResultMap" type="orders"> <!--两部分数据:订单+用户--> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <!--association:关联的意思,用于一对一关联封装数据 property:一对一关联后封装成的数据所对应的属性名 javaType:属性的类型 --> <association property="user" javaType="user"> <id column="user_id" property="id"/> <result column="username" property="username"/> <result column="address" property="address"/> </association> </resultMap>
l 测试程序
@Test public void testQueryOrdersUser() { SqlSession sqlSession = sqlSessionFactory.openSession(); OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class); List<Orders> ordersList = ordersMapper.queryOrdersUser(); if(ordersList != null && ordersList.size() > 0) { for (int i = 0; i < ordersList.size(); i++) { Orders orders = ordersList.get(i); System.out.println(orders); } } sqlSession.close(); }
一对多查询
需求:查询全部用户数据,关联查询出订单数据
l Mapper映射文件
<!--一对多用例--> <select id="queryUserOrders" resultMap="userOrdersResultmap"> SELECT u.`id`, u.`username`, u.`sex`, u.`birthday`, u.`address`, o.`id` oid, o.`user_id`, o.`number`, o.`createtime`, o.`note` FROM USER u LEFT JOIN orders o ON u.`id` = o.`user_id` </select> <resultMap id="userOrdersResultmap" type="user"> <!--两部分数据:用户+订单--> <id column="id" property="id"/> <result column="username" property="username"/> <result column="sex" property="sex"/> <result column="birthday" property="birthday"/> <result column="address" property="address"/> <!--一对多使用collection标签 property:封装成对应的属性的属性名 ofType:因为此时属性是一个list集合,collection已经表明了集合的意思,最重要的指定list里面的泛型类型, 所以使用ofType --> <collection property="ordersList" ofType="orders"> <id column="oid" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> </collection> </resultMap>
多对多查询
多对多关系分析
多对多是双向的一对多
实现Role到User的一对多
l Mapper映射文件
<select id="queryRoleUsers" resultMap="roleUsersResultMap"> SELECT r.rid, r.rname, r.rdesc, u.`username`, u.`address` FROM role r LEFT JOIN user_role ur ON r.`RID` = ur.`RID` LEFT JOIN USER u ON ur.`UID` = u.`id` </select> <!-- 多对多拆分成双向的一对多--> <resultMap id="roleUsersResultMap" type="role"> <id column="rid" property="rid"/> <result column="rname" property="rname"/> <result column="rdesc" property="rdesc"/> <collection property="userList" ofType="user"> <result column="username" property="username"/> <result column="address" property="address"/> </collection> </resultMap>
实现User到Role的一对多
[了解]扩展知识
Tomcat配置JNDI数据源(了解)
l 第一步:将数据库驱动程序(jar包)放到tomcat安装目录下的common\lib文件夹下
l 第二步:在Tomcat的conf/context.xml文件中进行jndi数据源服务配置
name:在JNDI中叫做目录名,等同于服务名,此处的jndi/mybatis是自定义的,往往以/连接前后字符串即可。
auth和type是固定的,其他都是数据库连接池的具体配置信息
l 第三步:在自己web项目的web.xml中引用Jndi数据源服务
l 第四步:在自己web项目的Mybatis配置文件中使用
配置data_source属性,指向你的数据源引用,java:comp/env/jndi/mybatis中红色部分是固定的,绿色部分是你自己定义的目录名(服务名)
工程准备
使用Mapper动态代理的方式实现:
l 根据用户id查询用户
l 查询全部订单数据并关联查询出用户数据(一对一)
l 查询全部用户数据并关联查询出订单数据(一对多)
便于基于代码程序讲解Mybatis延迟加载和缓存。
[掌握]Mybatis延迟加载策略
关联对象:数据库层次用户和订单是关联表,在对象层次订单对象和用户对象就叫做关联对象
什么是延迟加载?
在使用关联对象的过程中
比如使用了Orders对象,但是我只是获取Orders的订单数据尚未获取user的数据,但是依然查询了user表(这不是延迟)
我orders.getNumber()你不要查user表的数据,当我orders.getUser()你再去查user表
关联查询的关联对象,在较多的场合中并不需要的时候,只在少部分场合需要关联的那个对象数据,可以考虑使用延迟加载
Mybatis中怎么实现延迟加载?
步骤1:原来关联sql语句要拆分
1) 第一条sql查询基础表
2) 第二条sql根据基础表的条件查询关联表
步骤2:让两条sql自动产生联系,通过<association column="" select=""/> 或者<collection column="" select =""/>
l 全局延迟加载开关配置(一对一和一对多都需要开启)
<setting name="lazyLoadingEnabled" value="true" /><setting
name="aggressiveLazyLoading" value="false" /><setting name="lazyLoadTriggerMethods" value="true" />
l 一对一关联查询的延迟加载
<select id="queryOrdersWithUser" resultMap="ordersUserResultMap"> SELECT o.`id`, o.`user_id`, o.`number`, o.`createtime`, o.`note` FROM orders o </select> <!-- 延迟加载分析 1、原来关联查询的sql语句必然要拆分,如果不拆分,那肯定要执行关联查询,两个表用不用都要查 2、sql=sql1+sql2拆分之后,要让这两个sql自动产生联系 sql1:select id,user_id,number,createtime,note from orders sql2:select * from user u where id=user_id --> <resultMap id="ordersUserResultMap" type="orders"> <id column="id" property="id"/> <result column="user_id" property="userId"/> <result column="number" property="number"/> <result column="createtime" property="createtime"/> <result column="note" property="note"/> <association property="user" javaType="user" column="user_id" select="com.mybatis.mapper.UserMapper.queryUserByUserId"> </association> </resultMap> <select id="queryUserByUserId" parameterType="int" resultType="user"> select id,username,sex,birthday,address from user where id=#{user_id} </select>
l 一对多关联查询的延迟加载
<select id="queryUserWithOrders" resultMap="userOrdersResultMap"> SELECT u.`id`, u.`username`, u.`sex`, u.`birthday`, u.`address` FROM USER u </select> <resultMap id="userOrdersResultMap" type="user"> <id column="id" property="id"/> <result column="username" property="username"/> <result column="sex" property="sex"/> <result column="birthday" property="birthday"/> <result column="address" property="address"/> <collection property="ordersList" ofType="orders" column="id" select="com.mybatis.mapper.OrdersMapper.queryOrdersByUserId"> </collection> </resultMap> <!--拆分的第二条sql语句--> <select id="queryOrdersByUserId" parameterType="int" resultType="orders"> select o.id,o.user_id,o.number,o.createtime,o.note from orders o where o.user_id=#{id} </select>
[了解]Mybatis缓存
Mybatis的一级缓存
Mybatis的一级缓存是SqlSession级别的缓存,是默认开启的
注意:以下情形缓存会失效(被清理掉):
n sqlSession关闭
n sqlSession提交事务:意味着可能是一个增删改的动作,数据表数据产生变化,那么这个时候Mybatis就会把这个sqlSession已有的一级缓存给清理掉
Mybatis的二级缓存
l Mybatis二级缓存机制
l 怎么用Mybatis的二级缓存
n 开启二级缓存开关
第一个开关:需要在SqlMapConfig.xml中开启二级缓存总开关
第二个开关:需要在使用二级缓存的mapper.xml中开启(因为二级缓存是mapper级别的)
n Pojo实现序列化
ratio:命中率的意思,两次都去查询了二级缓存,但是第一次的时候缓存中没有数据,所以没有命中,第二次查询的时候缓存中已经有数据了,所以命中率是0.5
l Mybatis二级缓存的清理时机:当这个mapper.xml中执行了非select语句的时候,整个Mapper的缓存全部清除掉。
注意:避免使用二级缓存,因为二级缓存带来的好处远远比不上他所隐藏的危害
n 如果你针对user表的sql操作并不是全部都在UserMapper.xml中,使用缓存的结果可能会不正确。
n 多表操作一定不能使用缓存
为什么不能?
首先不管多表操作写到那个Mapper.xml下,都会存在某个表不在这个Mapper下的情况。
例如两个表:role和user_role,如果我想查询出某个用户的全部角色role,就一定会涉及到多表的操作
<select id="selectUserRoles" resultType="UserRoleVO"> select * from user_role a,role b where a.roleid = b.roleid and a.userid = #{userid} </select>
像上面这个查询,你会写到那个xml中呢??
不管是写到RoleMapper.xml还是UserRoleMapper.xml,或者是一个独立的
XxxMapper.xml中。如果使用了二级缓存,都会导致上面这个查询结果可能不正确。
如果你正好修改了这个用户的角色,上面这个查询使用缓存的时候结果就是错的。
[掌握]Mybatis的注解开发
效果:没有写sql语句的xml了
l 基础的crud(基础crud怎么使用注解)
l 关联查询延迟加载(怎么使用注解完成)
l 动态sql的注解方式
原则:把原来xml里面的内容都对应的放到注解上即可
注解开发不需要引入额外的jar包,和原来一致即可
注解添加在接口类的方法上
使用注解实现基本的CRUD
@Insert("insert into user(username,sex,birthday,address) values (#{username},#{sex},#{birthday},#{address})") /*回显mysql自增主键id 等同于原来我们xml中使用<selectKey/>,一一对应过来即可 * */ @SelectKey(statement = "select last_insert_id()",before = false,keyProperty = "id",resultType = Integer.class) void saveUser(User user); @Update("update user set username=#{username} where id=#{id}") void updateUsernameById(User user); @Delete("delete from user where id=#{id}") void deleteUserById(Integer id); @Select("select id,username,sex,birthday,address from user where id=#{id}") User queryUserById(Integer id); @Select("select id,username,sex,birthday,address from user") List<User> queryUserList(); @Select("<script>select id,username,sex,birthday,address from user" + "<where>" + "<if test=\"sex != null and sex != ''\">" + " and sex=#{sex}" + "</if>" + "<if test=\"username != null and username != ''\">" + " and username like #{username}" + "</if>" + "</where></script>") List<User> queryUserByWhere(User user); @Select("<script>select id,username,sex,birthday,address from user" + "<foreach collection=\"list\" open=\" where id in(\" close=\")\" separator=\",\" item=\"item\">" + "#{item}" + "</foreach></script>") List<User> queryUserByIdsList(List<Integer> ids);
使用注解实现复杂关系映射开发
使用注解实现一对一查询
/* * 对应关系 * @Results对应xml中的resultMap * @Result对应xml中的id/result * id:true/false,是否是主键,默认false * column:查询列名 * property:pojo属性名 * association:在注解中使用@Result+one=@One的方式去表达 * fetchType:延迟加载类型,默认是延迟加载,eager:积极加载 * */ @Results({ @Result(id = true,column = "id",property = "id"), @Result(column = "user_id",property = "userId"), @Result(column = "number",property = "number"), @Result(column = "createtime",property = "createtime"), @Result(column = "note",property = "note"), @Result(property = "user",column = "user_id",javaType = User.class,one=@One(select = "com.mybatis.mapper.UserMapper.queryUserByUserid",fetchType = FetchType.LAZY)) }) List<Orders> queryOrdersUser(); @Select("select id,username,sex,birthday,address from user where id=#{user_id}") User queryUserByUserid(Integer id);
使用注解实现一对多查询
l mapper接口
/* * 对应关系 * @Results对应xml中的resultMap * @Result对应xml中的id/result * id:true/false,是否是主键,默认false * column:查询列名 * property:pojo属性名 * collection:在注解中使用@Result+many=@Many的方式去表达 * xml中的ofType在注解中使用 javaType表达=List.class * fetchType:延迟加载类型,默认是延迟加载,eager:积极加载 * */ @Select("select id,username,sex,birthday,address from user") @Results({ @Result(id = true,column = "id",property = "id"), @Result(column = "username",property = "username"), @Result(column="sex",property="sex"), @Result(column="birthday",property="birthday"), @Result(column="address",property="address"), @Result( property="ordersList",javaType = List.class,column="id", many = @Many(select="com.mybatis.mapper.OrdersMapper.queryOrdersByUserid",fetchType = FetchType.LAZY)) }) List<User> queryUserOrders(); @Select("select id,number from orders where user_id=#{userId}") List<Orders> queryOrdersByUserid(Integer userId);