Mybatis框架学习
Java EE阶段
SSM(Spring +Spring MVC + Mybatis)
数据持久层框架:Mybatis,hibernate(SSH) 和数据打交道 -》mysql ->通过Java代码操作数据库=JDBC
Mybatis的学习大纲
• JDBC编程及存在问题
• Mybatis的介绍
• Mybatis原理
• Mybatis的使用Demo
• 配置详解
全局配置文件(mybatis-config.xml)
Mapper XML文件
• 接口的绑定形式:配置和注解
常用注解:@Insert、@Delete 、@Update、@Select
• 动态代理介绍及mybatis中动态代理的源码分析
• 动态SQL
if标签、where标签、trim(where、set)标签、foreach标签
• 高级映射
一对一映射
一对多映射
• 多对多的映射
• 延时加载
• 缓存机制
一级缓存
二级缓存
• 代码生成器:mybatis-generator
参考书籍
《Mybatis从入门到精通》
Mybatis官网:https://mybatis.org/mybatis-3/zh/index.html
JDBC编程及存在问题
JDBC编程步骤
数据准备
# 创建数据库
CREATE DATABASE test;
#创建表
CREATE TABLE `Student` (
`SID` int(11) NOT NULL,
`Sname` varchar(25) NOT NULL,
`Ssex` varchar(4) DEFAULT NULL,
`Sage` int(11) DEFAULT NULL,
PRIMARY KEY (`SID`),
UNIQUE KEY `SID` (`SID`),
KEY `idx_name` (`Sname`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
#数据
mysql> select * from Student;
+-----+----------+------+------+
| SID | Sname | Ssex | Sage |
+-----+----------+------+------+
| 1 | zhaol | nan | 21 |
| 2 | qiandian | nan | 20 |
| 3 | sunfen | nan | 21 |
| 4 | wulan | nv | 18 |
| 5 | CJ1210 | 1 | 11 |
| 6 | LG1213 | NULL | 2 |
| 7 | CJ1210 | 1 | NULL |
| 8 | GY1803 | NULL | NULL |
+-----+----------+------+------+
引入jar包
注:jar是通过maven管理
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
JDBC的基础代码回顾
通过SID查询学生的信息
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JDBCDemo {
public static void main(String[] args) throws Exception {
Connection connection = null;
Statement statement = null;
ResultSet rs = null;
try {
//加载mysql驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接
String DB_URL = "jdbc:mysql://localhost:3306/test";
String USER = "root";
String PASS = "123456";
connection = DriverManager.getConnection(DB_URL, USER, PASS);
//获取Statement
statement = connection.createStatement();
String sql = "SELECT * FROM Student where SID=1 ";
//执行查询操作
rs = statement.executeQuery(sql);
//处理结果集
while (rs.next()) {
String sid = rs.getString("SID");
String sname = rs.getString("Sname");
String ssex = rs.getString("Ssex");
String sage = rs.getString("Sage");
System.out.println(sid + ":" + sname+":"+ssex+":"+sage);
}
} finally {
//关闭资源、释放连接
if (rs != null)
rs.close();
if (statement != null)
statement.close();
if (connection != null)
connection.close();
}
}
}
JDBC存在的问题
1、在使用JDBC操作数据库前进行连接、操作完成之后关闭连接、并发性能有大的影响
解决:为了达到连接复用,采用连接池
2、SQL语句硬编码在Java代码中,需求改变需要改变Java代码本身
解决:将SQL和Java代码解耦合,将SQL语句放在配置文件中(XML),需求改变只需要修改配置文件即可
3、返回的结果集存在硬编码的问题
解决:将数据库中的数据集映射到Java对象
Mybatis的介绍
Mybatis前身ibatis,是Apache旗下的开源的数据持久层框架
MyBatis 是一款优秀的持久层框架
它支持自定义 SQL、存储过程以及高级映射。
MyBatis
免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
MyBatis
可以通过 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old
Java Objects,普通老式 Java 对象)为数据库中的记录。
Mybatis的原理
Mybatis的使用Demo
通过实际业务来研究:通过SID查询学生信息
引入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<!--mybatis配置-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
全局的配置文件(mybatis-config.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://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>
POJO类(Student.java)
```java
```java
```java
public class Student {
private Integer SID;
private String Sname;
private String Ssex;
private Integer Sage;
//省略getter和setter方法
}
Mapper接口文件(StudentMapper.java)
public interface StudentMapper {
/**
* 通过SID查询数据实体
* @param sid
* @return
*/
public Student selectStudentByID(int sid);
}
配置Mapper.xml文件(StudentMapper.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命令空间,随便写,一般保证命名空间唯一 -->
<mapper namespace="com.tulun.MybatisDemo.StudentMapper">
<!--查询标签:select-->
<select id="selectStudentByID" resultType="com.tulun.MybatisDemo.Student">
select * from Student where SID = #{sid}
</select>
</mapper>
修改全局配置文件(mybatis-config.xml)
将StudentMapper.xml文件路径配置上
<!--配置映射-->
<mappers>
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>
执行查询操作
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MybatisTLDemo {
public static void main(String[] args) throws IOException {
//mybatis配置文件
String resource = "mybatis-config.xml";
//通过mybatis提供的Resources类来得到配置文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建会话工厂,传输mybatis配置文件信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过反射机制来获取对应mapper实例
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//调用mapper实例下方法
Student student = mapper.selectStudentByID(1);
System.out.println(student);
}
}
步骤总结:
1、配置mybatis-config.xml全局配置文件(数据源、mapper)
2、创建SQLSessionFactory
3、通过SqlSessionFactory创建SqlSession对象
4、通过SqlSession操作数据库CRUD
添加日志
日志依赖jar包
<!--log4j日志文件-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
日志配置文件
创建log4j.properties文件
## debug 级别
log4j.rootLogger=DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout = org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d{yyyy-MM-dd-HH\:mm\:ss,SSS} [%t] [%c] [%p] - %m%n
log4j.logger.com.mybatis=DEBUG /
##输出sql 语句
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
日志打印
2020-05-10-09:18:42,188 [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction] [DEBUG] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6adede5]
2020-05-10-09:18:42,191 [main] [com.tulun.MybatisDemo.StudentMapper.selectStudentByID] [DEBUG] - ==> Preparing: select * from Student where SID = ?
2020-05-10-09:18:42,351 [main] [com.tulun.MybatisDemo.StudentMapper.selectStudentByID] [DEBUG] - ==> Parameters: 1(Integer)
2020-05-10-09:18:42,372 [main] [com.tulun.MybatisDemo.StudentMapper.selectStudentByID] [DEBUG] - <== Total: 1
配置详解
全局配置文件:mybatis-config.xml详解
官网:[https://mybatis.org/mybatis-3/zh/configuration.html#]
properties:读取外部资源
properties属性是可以对外部的配置进行动态替换
<properties resource="db.properties">
<property name="passwd" value="root"/>
</properties>
配置中的属性可以在整个配置文件中被用来替换需要动态配置的属性值
<!--数据源配置-->
<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://localhost:3306/test"/>
<property name="username" value="${usename}"/>
<property name="password" value="${passwd}"/>
</dataSource>
</environment>
</environments>
其中,passwd是有properties中的元素设定的值进行替换
username属性会有db.properties文件中对应的值进行替换
注意:
如果属性不至一个地方进行配置,mybatis将按照以下的顺序来加载:
• 首先读取在properties元素中指定的属性
• 然后根据properties元素中的resource/url属性指定的文件内容,并覆盖之前读取的同名的属性
• 最后读取作为方法参数传递的属性,并覆盖之前读取的同名属性
通过方法参数传递的属性具有最高优先级,resource或URL加载的属性次之,最低级的是properties元素体内的属性
setting:全局的参数配置
setting配置参数较多,这里介绍常用的三个参数
cacheEnable是在mybatis中二级缓存的开关
LazeLoadingEnable和aggressiveLazyLoading配置懒加载的开关配置
<!--全局参数配置-->
<settings>
<!--开启二级缓存的开关-->
<setting name="cacheEnable" value="true"/>
<setting name="lazyLoadingEnable" value="true"/>
</settings>
typeAliases:类型别名
类型别名是尾Java类型设置一个短的名字,它和XML配置文件有关,用来减少类完全限定名的冗余
<!--类型别名-->
<typeAliases>
<!--单个别名定义 type:pojo类的路径(全限定名) alias:别名的名称-->
<typeAlias type="com.tulun.MybatisDemo.Student" alias="student"/>
<!--批量的别名定义 name:指定的包名,将包下边的所有pojo类取别名,别名为类名(首字母大小写都行)-->
<package name="com.tulun.pojo"/>
</typeAliases>
在mapper.xml 文件中,定义了很多的Statement,Statement需要parameterType指定输入参数类型,需要resultType指定输出参数类型,如果指定类型为全路径,开发不方便,可以针对parameterType和resultType指定的类型取别名,别名在配置文件(mybatis-config.xml)中配置typeAliases没在mapper.xml文件中使用
typeHandlers:类型处理器
在进行参数映射成数据库表字段时或者是数据库字段映射成返回java类型时,涉及到类型映射问题,mybatis提供了合适的转换方式,如果不满足需求,可以重写类型处理器
plugins:插件
environments:环境配置
mybatis可以配置成适应多讴种环境,这种机制有主与SQL映射应用到多种数据库中,如开发,测试,线上环境需要不同的配置
可以配置多个环境,SQLSessionFactory实例是只能选取其中一种环境
<!--数据源配置-->
<environments default="test">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="${username}"/>
<property name="password" value="${passwd}"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="${username}"/>
<property name="password" value="${passwd}"/>
</dataSource>
</environment>
<environment id="online">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="${username}"/>
<property name="password" value="${passwd}"/>
</dataSource>
</environment>
</environments>
默认使用的环境 ID(比如:default=“development”)。
每个 environment 元素定义的环境 ID(比如:id=“development”)。
事务管理器的配置(比如:type=“JDBC”)。
数据源的配置(比如:type=“POOLED”)。
Mappers:映射器
定义的SQL,需要高速mybatis到哪里寻找SQL,使用相对类路径的资源引用
<!--配置映射 三种映射方式-->
<mappers>
<!--resource方式:单个mapper映射,xml和接口可以不再一个目录下
在StudentMapper.xml中定义namespace为mapper接口的地址,映射文件通过
namespace来找到mapper接口文件-->
<mapper resource="mapper/StudentMapper.xml"/>
<mapper resource="mapper/UsertMapper.xml"/>
<!--class方式:单个mapper映射,指定mapper接口的地址遵循规则
将mapper.xml和mapper.java放在同一个目录下,且文件名相同
-->
<mapper class="com.tulun.MybatisDemo.StudentMapper"/>
<mapper class="com.tulun.MybatisDemo.UserMapper"/>
<!--package方式:批量的mapper映射遵循的规则:将mapper.xml和mapper.java 放在
同一个目录下,且文件名相同-->
<package name="com.tulun.MybatisDemo"/>
</mappers>
Mapper XML 文件详解
Mapper XML文件的根标签:mapper,根标签存在属性:namespace:命名空间,一般是接口类的全限定名
主要介绍增\删\改\查标签
select标签
select:查询操作的标签
常用的属性的解释说明:
**id属性(必须):**当前命名空间下Statement的唯一标识,要求id和mapper接口中的方法保持一致
resultType(必须):将结果集映射为Java的对象,该属性值为映射对象的全限定名或者是别名(和resultMap二选一)
resultType和resultMap有什么区别?
parameterType(可以忽略)传入参数类型(入参还有parameterMap)
insert 标签
insert标签是用来添加数据的
insert常用的属性:
id属性(必须),唯一标识
parameterType(可以忽略)传入参数类型(入参还有parameterMap)
useGeneratedKeys(可以忽略)开启主键回写
update 标签
update标签是用来修改数据
update的常用的属性:
id属性(必须) 唯一标识
resultType和parameterType(可以忽略)
delete标签
delete标签是用来删除数据
常用的属性:
id属性(必须) 唯一标识
resultType和parameterType(可以忽略)
Jnuit测试介绍
Junit是用于编写和运行可重复的自动化测试的开源框架
Junit使用的步骤
引入Junit的依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
生成测试类
在Idea下,在需要测试类的当前的窗口,直接快捷点:ctrl+shfit+t ,选择create New Test
选择测试类
生成测试类
对以上两个接口进行测试
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() {
//mybatis配置文件
String resource = "mybatis-config.xml";
//通过mybatis提供的Resources类来得到配置文件流
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
//创建会话工厂,传输mybatis配置文件信息
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void selectStudentByID() {
//通过工厂得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过反射机制来获取对应mapper实例
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//调用mapper实例下方法
Student student = mapper.selectStudentByID(1);
System.out.println(student);
sqlSession.close();
}
@Test
public void insertStudent() {
//通过工厂得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过反射机制来获取对应mapper实例
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//插入数据
Student student1 = new Student();
student1.setSID(10);
student1.setname("tulun1");
student1.setSage(11);
student1.setSsex("nan");
mapper.insertStudent(student1);
//事务提交
sqlSession.commit();
//关闭资源
sqlSession.close();
}
常用的注解
@Before
当在测试方法之前需要执行一些操作可以放在@Before注解中
读取配置信息
@Test
@Test注解的会被当做测试用例,Junit每一次会创建一个新的测试实例,调用@Test注解方法
@After
After 注解是在Test注解调用之后才会进行执行,一般是进行资源的回收
Mybatis的使用方法
mybatis主要有两种使用方式,一种XML配置的形式,一种是注解的形式
XML配置形式用法
XML的使用步骤
select标签介绍
场景:以查询为例:通过ID查询学生信息
创建接口文件
public interface StudentMapper1 {
/**
* 通过SID查询用户信息
* @param sid
* @return
*/
public Student getStudentByID(int sid);
}
select * from Student where SID = ?
创建mapper.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.tulun.dao.StudentMapper1">
<!--select * from Student where SID = ?-->
<!--
select查询操作标签
id属性:对应接口中的方法名,必填项,且在同一个namespace下,id不能重复
#{XXX}:占位符,里面添加的是方法中的参数名
parameterType属性:指定入参类型
resultType:执行返回参数类型,指定全路径名
resultMap属性:指定返回参数类型
-->
<select id="getStudentByID" parameterType="java.lang.Integer" resultType="com.tulun.pojo.Student">
select * from Student where SID = #{sid}
</select>
</mapper>
配置映射路径
在全局配置文件中配置当前的mapper.xml文件
<mappers>
<mapper resource="mapper/StudentMapper1.xml"/>
</mappers>
测试接口
public class StudentMapper1Test {
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() {
//mybatis配置文件
String resource = "mybatis-config.xml";
//通过mybatis提供的Resources类来得到配置文件流
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
//创建会话工厂,传输mybatis配置文件信息
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void getStudentByID() {
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过接口产生代理类
StudentMapper1 mapper = sqlSession.getMapper(StudentMapper1.class);
Student student = mapper.getStudentByID(2);
System.out.println(student);
}
}
resultType和resultMap的区别:
使用resultType是需要数据库字段和Java类的类型名保持一致,如果不一致则相应属性无法完成映射
使用resultMap进行显性映射,可以解决数据库字段和Java类属性名不一致的问题
Insert标签
场景:插入一个学生信息
insert into Student(SID,Sname,Ssex,Sage ) values(?,?,?,?);
在接口中声明方法
/**
* 插入单个学生信息
* @param student
* @return
*/
public int insertStudent(Student student);
在Mapper.xml中开发
<!--插入单个学生信息-->
<!--
insert标签:插入操作
id属性:在当前的namespace中唯一标识
parameterType:传入参数的类型,是参数类型的全限定名,可选操作
flushCache属性:和一级二级缓存有关,true和false值,默认值true:在调用该语句是,会清空缓存
timeout:等待数据返回的超时时间
useGeneratedKeys属性:使用数据内部产生的主键 默认为false
keyColumn:指定数据库中的生成键
-->
<insert id="insertStudent" parameterType="com.tulun.pojo.Student" >
insert into Student(SID,Sname,Sage, Ssex) values (#{SID},#{name},#{Sage},#{Ssex})
</insert>
执行过程
如果使用数据库自增主键
update和delete标签
注意:在编写Mapper的文件是需要遵循的开发规范,mybatis才能自动的生成接口的代理对象
开发规范
1、在mapper.xml文件中namespace和Mapper的接口文件的接口名保持一致
<mapper namespace="com.tulun.dao.StudentMapper1">
2、Mapper.java接口中的方法名和Mapper.xml中的Statement的ID保持一致
3、Mapper.java接口中方法的输入参数和mapper.xml文件中Statement的parameterType指定的类型一致
4、mapper.java接口中方法的返回值类型和mapper.xml文件中Statement的resultType指定的类型一致
注解形式的用法
注解的形式SQL语句直接卸载接口上
优点:比较简单
缺点:当SQL发生改变,需要重新编译代码
select
public interface StudentMapper2 {
/**
* 通过SID查询学生信息
*/
@Select("select * from Student where SID=#{sid}")
public Student getStudentBySID(int sid);
}
执行的结果:
查询操作使用注解@Select,在方法上直接写SQL
如果数据库字段和Java类属性不一致,使用@Results
执行结果:
和XML配置形式比和ResultMap标签类似:
Insert
场景:插入单个学生信息
/**
* 插入学生信息
*/
@Insert("insert into Student(SID,Sname,Sage,Ssex) values(#{SID},#{name},#{Sage},#{Ssex})")
public int insertStudent(Student student);
执行过程:
使用mybatis自增主键
强调:
Java进行Mybatis调用时:
sqlSessionFactorybuilder:mybbatis提供的创建sqlSessionFactory实例的类,通过读取配置文件来创建sqlSessionFactory
**sqlSessionFactory:**会话工厂,一般是用单例模式创建,用来创建sqlSession会话的
**sqlSession:**会话,是操作数据库的CRUD等,sqlSession不安全的,一般进行一次操作就使用一个新的会话
Update
Delete
多个参数的传递
当传递多个参数时,需要注意的问题:
通过学生年龄和性别来查询学生信息
select * from Student where Sage=? and Ssex=?
mybattis配置如下:
执行会抛出异常:
XML可用的参数只有0,1,param1和Param2,Mybatis根据位置自定义的名字,可以将#{sex}改为#{0}或者#{param1}
多参数传递是给参数配置@Param注解
@Select("select * from Student where Ssex=#{sex} and Sage = #{age}")
public Student getStudentByAgeAndSex(@Param("sex") String sex,@Param("age") int age);
配置注解后,mybatis自动的将参数封装成Map类型,@Param注解值作为Map中的key,参数值作为Map中的value,通过这种形式可以识别多参数的对应关系
#{}和${}区别:
#{}使用
@Select("select * from Student where SID=#{sid}")
public Student getStudentBySID(int sid);
日志打印:
Preparing: select * from Student where SID=?
Parameters: 2(Integer)
#{}占位符在执行过程中将#{}替换重"?"占位符,将参数值和SQL分开传递到服务器
#{}使用类似于JDBC编程中prepareStatement
${}使用
参数的获取使用ognl表达式,使用的参数必须提供getteer方法
@Select("select * from Student where SID=${SID}")
public Student getStudentBySID(Student student);
打印日志:
Preparing: select * from Student where SID=1
Parameters:
使用${}方式传递参数时,直接将参数拼接到SQL
${}使用JDBC编程中的Statement的操作
SQL注入问题:
#{}不存在SQL注入问题,采用预编译机制,将SQL和参数分别传递给SQL服务器
${}存在SQL注入问题
一般情况时使用#{}
动态SQL
Mybatis强大特征之一自安于他的动态SQL,采用是OGNL表达式来处理SQL,根据表达式的不同能够将SQL进行拼接和组装
主要动态SQL的标签if标签,where标签,trim(where,set)标签,foreach标签
if标签
场景1:根据SID查询学生
场景2:根据年龄查询学生
场景3:根据性别查询学生
场景4:更加年龄和性别查询学生
<!--
if表达式
一般使用是放在where条件后面
判断参数是否传递使用if test属性(必填)为true或者false
test使用OGNL表达式处理,返回true则进入到if标签的SQL,返回为false则不会进入if标签
参数处理:
假如不存Sage,Ssex: select * from Student where 1=1
假如传Sage,Ssex :select * from Student where 1=1 and Sage = #{Sage} and Ssex = #{Ssex}
假如传Sage:select * from Student where 1=1 and Sage = #{Sage}
假如传Ssex:select * from Student where 1=1 and Ssex = #{Ssex}
-->
<select id="getStudentBySexAndAge" resultType="com.tulun.pojo.Student">
select * from Student where 1=1
<if test="Sage != 0">
and Sage = #{Sage}
</if>
<if test="Ssex != null">
and Ssex = #{Ssex}
</if>
</select>
测试:
打印日志:
传递1一个参数:Sex
打印日志:
传递1一个参数:age
打印日志:
不传参数:
日志打印:
where标签
where标签:一般和if标签一块使用,如果标签包含的元素有返回值就插入where,将紧随where后面的And或OR开头的,将他们剔除
<!--where标签-->
<!--
值传递Sage:select * from Student where Sage = #{Sage};
不传递:select * from Student
两个都传递:select * from Student where Sage = #{Sage} and Ssex = #{Ssex}
-->
<select id="getStudentBySexAndAge" resultType="com.tulun.pojo.Student">
select * from Student
<where>
<if test="Sage != null">
and Sage = #{Sage}
</if>
<if test="Ssex != null">
and Ssex = #{Ssex}
</if>
</where>
</select>
测试:
无参
打印日志:
传递一个参数:
打印日志:
传输传递多个:
日志打印:
trim(where\set)标签
trim标签用于取出SQL中多余And关键字,逗号,使用在where或者set中
属性 描述
prefix:给SQL拼接的前缀
suffix:给SQL语句拼接的后缀
prefixOverrides:剔除SQL语句前关键字后者字符
suffixOverrides:去除SQL语句后面的关键字或者字符
<!--trim标签(where)-->
<select id="getStudentBySexAndAge" resultType="com.tulun.pojo.Student">
select * from Student
<trim prefix="where" prefixOverrides="and">
<if test="Sage != null">
and Sage = #{Sage}
</if>
<if test="Ssex != null">
and Ssex = #{Ssex}
</if>
</trim>
</select>
测试:
传递多个参数:
打印日志:
foreach标签
批量处理
场景:通过一批SID查询用户信息
select * from Student where SID in(1,2,3);
insert into Student (SID,Sname) values (1,“zhansan”), (2,“zhansan”), (2,“zhansan”);
接口文件中的方法:
/**
* 批量查询
*/
public List<Student> batchSelectStudentByIds(List<Integer> ids);
Mapper.xml文件
<!--批量查询SQL-->
<!--
foreach表达式
collection属性(必填)指定输入参数类型
list:列表
array:数组
map:map集合
item属性:取名称,给集合中单个元素的名称
open属性:开始的字符串
close属性:结束的字符串
separator属性:数据之间的分割符
-->
<select id="batchSelectStudentByIds" resultType="com.tulun.pojo.Student">
-- select * from Student where SID in(1,2,3);
select * from Student where SID in
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
测试:
日志打印:
模糊匹配
需求:查询Student表,表中含有"L”的所有用户
SQL:select * from Student where Sname like “%L%”;
方式1:直接在参数上拼接通配符
mapper.getStudentsByName("%L%");
mapper.xml文件
将通配符直接拼接在方法参数上
日志打印:
方法2:Mysql中的concat(,)
concat(par1,par2) :进行字符串拼接
Mapper.xml配置
测试用例:
日志打印:
方法3:bind表达式处理
Mybatis提供的bind表达式
mapper.xml文件配置
接口方法:日志打印:
动态代理
原生的方法调用直接通过sqlSession方法调用:提供了selectOne,selectList,Insert,delete…方法
//原有方法调用形式 ,/** * 查询操作; * 返回多个结果时,使用selectList * 返回的结果不管是单个还是多个在resultType属性都是返回的Java对象全路径 * * 返回单个结果对象使用selectOne */sqlSession.selectList(“com.tulun.dao.StudentMapper1.getStudentsByName”, “%L%”);
代理模式是Java中的一种设计模式
公司里开发的一款软件产品,产品的开发是有开发人员开发。由销售人员去售卖产品,产品时有销售卖给顾客。
代理模式中代理类和委托类是具有相同的接口
代理类的主要职责就是为委托类预处理消息,过滤消息,等功能
代理类的对象本身并不是真正的实现服务,而是通过委托类的对象的相关方法,来提供特定的一些服务
代理类和委托类之间存在关联关系的,一个带来的对象和一个委托类的对象向关联
访问实际对象,是通过代理对象类方法的
代理模式的话分为静态代理和动态代理
静态代理是在程序编译阶段就确定了代理对象
动态代理是在程序运行阶段确定代理对象
动态代理是在运行时根据Java代码指示动态生成的,相比较静态代理,优势在在于方便的对代理类的函数进行统一的处理,而不用修改每个代理类的方法
Java中提供的动态代理方式有两种:JDK自带的动态代和CGLib实现代理
JDK自带的代理方式
JDK自带的代理方式是需要实现invocationHandler接口,实现invoke的方法
JDK自带代理的代码演示
先提供一个共有的接口和委托类实现
接口:
/**
* 接口类
* 定义委托类和代理类共工的方法
*/
public interface IUser {
void talk();
}
实现类:
/**
* 委托类
* 实现了接口Iuser中的talk方法
*/
public class User implements IUser {
@Override
public void talk() {
System.out.println("doing User.talk");
}
}
实现动态代理,首先创建一个实现了InvocationHandler接口的辅助类
/**
* 代理的辅助类
*/
public class UserProxy implements InvocationHandler {
private Object object;
public UserProxy(Object o) {
this.object = o;
}
/**
* 实现动态代理,就需要实现InvocationHandler接口中的invoke方法,该方法有三个参数
* @param proxy :就是动态代理生成的代理对象
* @param method:就是调用的方法
* @param args:表示该方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理类提供委托类提供的功能,也可以提供自己特有的功能
System.out.println("donging UserProxy.invoke");
//调用委托类提供的方法
method.invoke(object,args);
System.out.println("donging UserProxy.invoke end");
return null;
}
}
使用产生代理对象时,需要调用代理辅助类,调用委托方法
public static void main(String[] args) {
//产生代理对象
IUser iUser = (IUser) Proxy.newProxyInstance(UserProxy.class.getClassLoader(), new Class[]{IUser.class}, new UserProxy(new User()));
iUser.talk();
}
日志打印:
通过日志的观察,当前的代理类调用talk方法,调用到代理辅助类UserProxy中的invoke方法,还调用到了委托类的talk实现
JVM是如何自动实现invoke方法的调用呢?
JDK自带代理方式的原理探究
在代理类面方法调用中添加一个监控操作
//看看JDK生成的自动代理类
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
通过执行后查看,JVM生成了一个$Proxy0.class文件,源码如下:
P
r
o
x
y
0
的
定
义
中
可
知
,
确
实
实
现
了
I
U
s
e
r
接
口
。
和
代
理
模
式
下
的
代
理
是
完
全
一
致
的
!
[
在
这
里
插
入
图
片
描
述
]
(
h
t
t
p
s
:
/
/
i
m
g
−
b
l
o
g
.
c
s
d
n
i
m
g
.
c
n
/
20200529230945204.
p
n
g
)
通
过
分
析
:
当
代
理
类
实
例
中
调
用
方
法
t
a
l
k
时
,
根
据
J
a
v
a
中
的
多
态
,
代
用
了
应
该
是
Proxy0的定义中可知,确实实现了IUser接口。和代理模式下的代理是完全一致的 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200529230945204.png) 通过分析:当代理类实例中调用方法talk时,根据Java中的多态,代用了应该是
Proxy0的定义中可知,确实实现了IUser接口。和代理模式下的代理是完全一致的![在这里插入图片描述](https://img−blog.csdnimg.cn/20200529230945204.png)通过分析:当代理类实例中调用方法talk时,根据Java中的多态,代用了应该是Proxy0的talk方法,$Proxy0重写talk方法
CGLib动态代理
代理提供了一种可扩展机制来控制被代理对象的访问,就是对象的访问做了一层封装
当代理没有接口的类,此时Proxy和InvocationHandler机制不能使用了(JDK自带的代理模式的使用必须需要右接口的),此时可以使用CGLib库,采用底层字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截父类所有方法的调用,采用横切的逻辑。Spring AOP(横向切面)的技术技术就是使用动态代理
CGLib的使用需要引入第三方库
<!--CGLib-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.2</version>
</dependency>
父类:
public class CGLibSuper {
public void doing() {
System.out.println("CGLibSuper.doing");
}
}
辅助类的实现:
/**
* 通过已有的类产生代理类时
* 在当前辅助类需要实现MethodInterceptor接口
*/
public class CGLibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public <T> T getProxy(Class<T> clazz) {
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码动态的创建子类实例
return (T)enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLibProxy doing");
//拦截到父类响应的方法
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("CGLibProxy doing end");
return o1;
}
}
通过CGLib实现代理
public static void main(String[] args) {
CGLibProxy cgLibProxy = new CGLibProxy();
//动态产生的CGLibSuper的子类实例
CGLibSuper proxy = cgLibProxy.getProxy(CGLibSuper.class);
proxy.doing();
}
执行结果:
通过执行结果可知,当前产生的代理类调用相应的方法(doing),会被代理辅助类中的intercept方法拦截,在该方法中可以实现扩展,器也能调用到父类中的相应方法
Mybatis的代理模式详解
mybatis中例产生的代理是那种方式???
JDK自带的方式:仅在其中实现了接口
mapper是如何添加进入的?
通过代码形式产生的会话工厂实例和读取配置形式是类似的,
通过代码形式中可以看到,Mapper接口是通过Configuration.addMapper()形式来添加,参数为接口的class
类似于:
Configuration configuration = new Configuration();
configuration.addMapper(StudentMapper.class);//添加mapper
addMapper方法的实现:
Configuration类中:
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
mapper实际上被添加到了MapperRegistry中,继续
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {//只添加接口
if (hasMapper(type)) {//不允许接口重复添加
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));//将接口存放到HashMap中,重点
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
执行的Configuration.addMapper()操作,最终是被放入到HashMap中,其名为knownMappers,knownMappers是MapperRegistry中的属性,他是一个HashMap对象,key为当前Class对象,Value为MapperProxyFactory实例
从getMapper入手分析:
SQLSession中调用getMapper()方法操作实际调用到DefaultSqlSession中的实现
DefaultSqlSession类:
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
其中DefaultSqlSession没有逻辑,直接调用configuration中的getMapper方法,configuration是将配置信息存放中
configuration类中的操作:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
其中getMapper的实现是调用mapperRegistry中的方法,知道,mapperRegistry是存放了一个HashMaP的
mapperRegistry类:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);//重点看这里,通过这个操作返回了代理对象
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
调用SQLSession.getMapper操作,最终会到上面的这个方法,他会根据接口在HashMap中找到对应的Value值,是一个MapperProxyFactory的对象,然后通过调用该对象newInstance的方法,获取到代理对象
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//JDK自带的代理方式生成映射器代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
public class MapperProxy<T> implements InvocationHandler, Serializable {
//当前类实现了InvocationHandler接口
//实现invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);//其中实现是调用委托类方法
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);//使用缓存???
//执行CRUD相关操作
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
Mapper是如何注册的,Mapper接口是如何产生动态代理对象的?
Mapper接口在初始sqlSessionFactory的时候进行注册
Mapper接口注册到了mapperRegistry类HashMap中,key是Mapper的class,value是当前的Mapper工厂
Mapper注册之后,可以从sqlSession中get获取对象
sqlSession.getMapper使用了JDK自带的动态代理,产生目标接口Mapper的代理对象
动态代理的代理辅助类MapperProxy
Mybatis提供的缓存机制
缓存介绍
缓存提供一种存储方式,将数据存储,访问数据是可以直接访问缓存,减轻数据库的压力,提高数据库性能
服务器和数据库是进行数据交互,无非是增删改查,查询操作比较多,查询操纵在数据库获取数据结果集,并不会引起数据本身的改变,增、删、改会改变数据库中的数据。
查询操作的特点:频率高,不会改变数据
服务端到数据库是要进行网络交互。缓存可以减少服务端和数据库的交互,即减轻数据库压力,提高数据库的性能,缓存主要是用于查询缓存。
脏数据:
举例:
查询id=1查询到用户信息,放入到缓存,后续对id=1的查询就可以直接查询缓存数据
对id = 1 的数据进行变更(用户id=1的电话做了变更),数据的变更数据库
变更后,在查询id=1的时候,查询缓存,缓存中就存在为更新的旧数据
使用缓存是注意:一定要避免脏数据问题
缓存更新时机:
缓存数据做变更?涉及缓存的更新时机:
查询操作:首先访问缓存,当缓存没有对应数据时,在查询数据,将数据库的中数据查询到后,插入到缓存中,后续的插叙就可以直接访问缓存
在变更操作时(修改,删除等)将数据变更到数据库时,直接将对应的缓存信息清空(hamp.remove(id))
再次查询时,访问缓存没有数据,就查询数据库将数据库返回的结果放入缓存,通过该更新机制能够保证缓存数据是最新的数据
缓存的实现方式:
1、在单实例服务器下,可以在当前服务代码中通过添加HashMap集合实例来充当缓存
在集群服务中,本地缓存(HashMap)无法将缓存数据共享给其他的服务器
2、在多服务器下,需要通过缓存共享的形式来缓存数据,使用缓存服务器处理,缓存服务器的代表:Redis,memCache等
Mybatis缓存
Mybatis提供一级缓存、二级缓存
一级缓存是SQLSession级别的缓存,在操作数据库是需要构造SQLSession会话对象,对同一个对象中的数据可以使用到缓存,不同的SQLSession之间的缓存是不同享的
二级缓存Mapper级别的缓存(namespace),多个SQLSession是共享缓存的
一级缓存
一级缓存是SQLSession级别的缓存,作用于在同一个SQLSession当中,在同一个SQLSession中连续执行同一个SQL语句,第一次执行的结果放入缓存,第二次操作就可以直接访问缓存结果
Mybatis是默认支持一级缓存,不需要做相关配置
case1:同一个sqlSession下连续的执行同一个查询操作
case2:在同一个SQLSession下,先进行查询,在变更操作,然后进行同一个SQL查询
case3:不同的sqlsession下,同一个SQL查询操作
case1验证:
测试代码:
//case1:同一个sqlSession下连续的执行同一个查询操作
@Test
public void getStudentByID() {
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过接口产生代理类
StudentMapper1 mapper = sqlSession.getMapper(StudentMapper1.class);
//第一次执行查询操作,查询id =1
Student student = mapper.getStudentByID(1);
System.out.println(student);
//第二次查询
Student student1 = mapper.getStudentByID(1);
System.out.println(student1);
}
日志打印:
在同一个SQLSession下,连续的查询同一个SQL语句,值查询一次数据库,第二次查询即访问缓存拿到结果
case2:
//case2:在同一个SQLSession下,先进行查询,在变更操作,然后进行同一个SQL查询
@Test
public void getStudentByID2() {
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过接口产生代理类
StudentMapper1 mapper = sqlSession.getMapper(StudentMapper1.class);
//第一次执行查询操作,查询id =1
Student student = mapper.getStudentByID(1);
System.out.println(student);
//进行更新SID=1的学生信息
Student student2 = new Student();
student2.setSID(1);
student.setname("tulun1");
mapper.updateStudent(student);
sqlSession.commit();
//第二次查询
Student student1 = mapper.getStudentByID(1);
System.out.println(student1);
}
日志打印:
在第一次查询之后,会将数据缓存起来,进行了变更操作时,将缓存清空,第二次执行同一个SQL的查询操作时,缓存不存在了,就会继续查询数据库
case3:
//case3:不同的sqlsession下,同一个SQL查询操作
@Test
public void getStudentByID3() {
//创建SQLSession实例1
SqlSession sqlSession1 = sqlSessionFactory.openSession();
StudentMapper1 mapper1 = sqlSession1.getMapper(StudentMapper1.class);
//创建SQLSession实例2
SqlSession sqlSession2 = sqlSessionFactory.openSession();
StudentMapper1 mapper2 = sqlSession2.getMapper(StudentMapper1.class);
//第一次执行查询操作
Student student = mapper1.getStudentByID(1);
System.out.println(student);
//第二次查询
Student student1 = mapper2.getStudentByID(1);
System.out.println(student1);
}
日志打印:
在同一个sqlsession下,连续查询操作可以使用缓存,在不同的SQLSession实例下,缓存是不共享的
二级缓存
二级缓存是Mapper级别的缓存,默认情况下二级缓存是关闭的
同一个Mapper下不同的sqlSession是可以共享二级缓存
不同Mapper缓存是相互隔离的
二级缓存和一级缓存的区别:
二级缓存范围更大,多个sqlSession可以共享一个Mapper的二级缓存区域
二级缓存使用步骤
1、需要在全局配置文件中打开二级缓存的开关
<settings>
<!--开启二级缓存的开关-->
<setting name="cacheEnabled" value="true"/>
</settings>
2、将映射的pojo类实现序列化
public class Student implements Serializable
3、在配置文件中添加cache标签
<mapper namespace="com.tulun.dao.StudentMapper1">
<!--开启本Mapper的二级缓存-->
<cache></cache>
二级缓存的测试
case1:在同一个Mapper实例下,不同的sqlSession执行同一个查询操作
case1:
//case3:在同一个Mapper实例下,不同的sqlsession下,同一个SQL查询操作
@Test
public void getStudentByID3() {
//创建SQLSession实例1
SqlSession sqlSession1 = sqlSessionFactory.openSession();
StudentMapper1 mapper1 = sqlSession1.getMapper(StudentMapper1.class);
//创建SQLSession实例2
SqlSession sqlSession2 = sqlSessionFactory.openSession();
StudentMapper1 mapper2 = sqlSession2.getMapper(StudentMapper1.class);
//第一次执行查询操作
Student student = mapper1.getStudentByID(1);
//执行关闭操作,将SQLSession中的数据写入到二级缓存中
sqlSession1.close();
System.out.println(student);
//第二次查询
Student student1 = mapper2.getStudentByID(1);
System.out.println(student1);
}
日志的打印:
在同一个Mapper下,不同的SQLSession实例可以共享缓存
Cache的参数说明
<!--
cache参数说明:
flushInterval(缓存刷新间隔)单位毫秒,默认情况下不设置,在变更操作时,进行缓存刷新
size:(引用次数)记录缓存对象的数量 默认值1024
readOnly(只读)参数为true:false 表示缓存的数据对象实例是不能被修改
eviction:缓存失效的策略 默认LRU
LRU:最近最少使用的:移除最长时间不使用的
FIFO:先进先出,按照缓存对象的顺序来淘汰
SOFT:软引用
WEAK:弱引用
-->
<cache eviction="LRU" readOnly="true" flushInterval="10000" size="12"></cache>
Cache的配置禁用
useCache=“false”
在statement上配置useCache=“false”,禁用的是二级缓存
缓存源码跟踪:
第一次执行:
第二次执行:
Mybatis 的高级映射
在多表联合查询操作时,就存在一对一、一对多、多对多的关系
以订单数据模型为例进行分析
每张表中的数据内容
每张表的主要的字段:外键字段,非空字段
表与表之间的业务关系
数据库表:
user:用户表:记录购买商品的用户信息
Orders:订单表,记录用户创建的所有订单
orderdetails:记录订单的详细的购买信息
items:商品表:记录商品信息
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL COMMENT '用户名称',
`sex` char(1) DEFAULT NULL COMMENT '性别',
`address` varchar(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8
CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '下单用户id',
`number` varchar(32) NOT NULL COMMENT '订单号',
`createtime` datetime NOT NULL COMMENT '创建订单时间',
`note` varchar(100) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`),
KEY `FK_orders_1` (`user_id`),
CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8
//订单明细表
CREATE TABLE `orderdetail` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`orders_id` int(11) NOT NULL COMMENT '订单id',
`items_id` int(11) NOT NULL COMMENT '商品id',
`items_num` int(11) DEFAULT NULL COMMENT '商品购买数量',
PRIMARY KEY (`id`),
KEY `FK_orderdetail_1` (`orders_id`),
KEY `FK_orderdetail_2` (`items_id`),
CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`items_id`) REFERENCES `items` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8
CREATE TABLE `items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL COMMENT '商品名称',
`price` float(10,1) NOT NULL COMMENT '商品定价',
`detail` text COMMENT '商品描述',
`pic` varchar(64) DEFAULT NULL COMMENT '商品图片',
`createtime` datetime NOT NULL COMMENT '生产日期',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
测试数据
insert into `items`(`id`,`name`,`price`,`detail`,`pic`,`createtime`) values (1,'台式机',3000.0,'该电脑质量非常好!!!!',NULL,'2015-02-03 13:22:53'),(2,'笔记本',6000.0,'笔记本性能好,质量好!!!!!',NULL,'2015-02-09 13:22:57'),(3,'背包',200.0,'名牌背包,容量大质量好!!!!',NULL,'2015-02-06 13:23:02');
/*Data for the table `user` */
insert into `user`(`id`,`username`,`sex`,`address`) values (1,'王五','2',NULL),(10,'张三','1','北京市'),(16,'张小明','1','陕西西安'),(22,'陈小明','1','陕西西安'),(24,'张三丰','1','陕西西安'),(25,'陈小明','1','陕西西安'),(26,'王五',NULL,NULL);
/*Data for the table `orders` */
insert into `orders`(`id`,`user_id`,`number`,`createtime`,`note`) values (3,1,'1000010','2015-02-04 13:22:35',NULL),(4,1,'1000011','2015-02-03 13:22:41',NULL),(5,10,'1000012','2015-02-12 16:13:23',NULL);
/*Data for the table `orderdetail` */
insert into `orderdetail`(`id`,`orders_id`,`items_id`,`items_num`) values (1,3,1,1),(2,3,2,3),(3,4,3,4),(4,4,2,3);
数据库模型分析
一对一映射
需求:通过订单号查询订单及用户信息
分析:通过分析:订单号是在订单表中,通过订单表中的user_id字段可以查询到用户信息
主表:orders 从表:user
SQL:select * from orders o,user u where o.user_id = u.id and o.number=?
Pojo类中添加User类型的一个属性
public class Orders10 {
private int id;
private int userId;
private long number;
private Date createtime;
private String note;
//增加一个User类型
private User10 user;
//getter和setter方法
接口:
public Orders10 getOrdersByNumber(String number);
Mapper的XML文件
使用resultType一对一映射
<select id="getOrdersByNumber" parameterType="java.lang.String" resultType="com.tulun.pojo.Orders10">
select o.*,u.id "user.id",u.username "user.username",u.sex "user.sex",u.address "user.address" from orders o,user u where o.user_id = u.id and o.number= #{number};
</select>
使用resultMap配置一对一映射
<resultMap id="OrderUserResultMap" type="com.tulun.pojo.Orders10">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime" javaType="java.util.Date" jdbcType="TIMESTAMP"/>
<result column="note" property="note"/>
<!--user配置-->
<result column="u_id" property="user.id"/>
<result column="u_username" property="user.username"/>
<result column="u_sex" property="user.sex"/>
<result column="u_address" property="user.address"/>
</resultMap>
<select id="getOrdersByNumber" parameterType="java.lang.String" resultMap="OrderUserResultMap">
select o.*,u.id u_id,u.username u_username,u.sex u_sex,u.address u_address from orders o,user u where o.user_id = u.id and o.number= #{number};
</select>
使用resultMap提供的association配置一对一关系
<resultMap id="OrderUserResultMap" type="com.tulun.pojo.Orders10">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime" javaType="java.util.Date" jdbcType="TIMESTAMP"/>
<result column="note" property="note"/>
<!--
association:用于映射关联查询单个对象的信息
property:关联查询映射到对应的自定义的对象的属性
javaType:映射的Java属性的全限定名
-->
<association property="user" columnPrefix="u_" javaType="com.tulun.pojo.User10">
<!--user配置-->
<result column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
</association>
</resultMap>
<select id="getOrdersByNumber" parameterType="java.lang.String" resultMap="OrderUserResultMap">
select o.*,u.id u_id,u.username u_username,u.sex u_sex,u.address u_address from orders o,user u where o.user_id = u.id and o.number= #{number};
</select>
优化1:使用extends属性继承当前的主类
<!--优化1:使用association关联映射对象,extends继承主类-->
<resultMap id="OrderUserResultMap" extends="OrdersResultMap" type="com.tulun.pojo.Orders10">
<association property="user" columnPrefix="u_" javaType="com.tulun.pojo.User10">
<!--user配置-->
<result column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
</association>
</resultMap>
优化2:使用association中的resultMap的属性
<!--优化2:使用association关联映射对象,extends继承主类,使用association标签下resultMap的属性-->
<resultMap id="OrderUserResultMap" extends="OrdersResultMap" type="com.tulun.pojo.Orders10">
<!--association的resultMap属性执行已存在Map的全路径名-->
<association property="user" columnPrefix="u_" resultMap="com.tulun.dao.UserMapper10.ResultUserMapper"/>
</resultMap>
一对一映射中,使用到resultMap的association标签
resultMap和resultType的区别?
一对多映射
需要关联映射是一个list结果
需求:查询用户的订单信息(例如:通过用户ID)
分析:通过用户(主表:user)的关键信息查询所有相关的订单(从表:orders)
SQL:select u.,o. from user u,orders o where u.id=o.user_id and u.id = ?
映射对象
public class User10 {
private int id;
private String username;
private String sex;
private String address;
//增加一个Order类型的集合
private List<Orders10> orders10s;
}
XML文件配置
使用resultMap中的Collection配置一对多关系
Collection:将关联查询的多条记录映射到集合兑现中
property:将关联查询的多条记录映射到的属性名:orders10s
ofType:执行映射的集合属性中的自定义类型
<!--
使用resultMap中的Collection配置一对多关系
Collection:将关联查询的多条记录映射到集合兑现中
property:将关联查询的多条记录映射到的属性名:orders10s
ofType:执行映射的集合属性中的自定义类型
extends:继承主类性
-->
<resultMap id="ResultUserOrdersMapper" extends="ResultUserMapper" type="com.tulun.pojo.User10">
<!--Orders的结果集-->
<collection property="orders10s" columnPrefix="order_" ofType="com.tulun.pojo.Orders10">
<result 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"/>
</collection>
</resultMap>
<sql id="selectId">
u.id,
u.username,
u.sex,
u.address,
o.id order_id,
o.user_id order_user_id,
o.number order_number,
o.createtime order_createtime,
o.note order_note
</sql>
<select id="getUserOrdersById" parameterType="int" resultMap="ResultUserOrdersMapper">
select <include refid="selectId"/>
from user u ,orders o where u.id=o.user_id and u.id=#{uid};
</select>
一对多的映射:使用到resultMap下Collection标签
Mybatis的xml文件中SQL片段的复用示例:
多对多的映射
需求:查询用户及用户的商品信息
分析:主表是User
存在用户表对象User,存在订单表对象Orders,存在订单明细表对象OrderDetail,商品表对象Items
映射的对象为User对象,在User对象中添加一个订单列表的属性:List ,将用户创建的订单映射到orderlist,
在Orders中添加订单明细的属性List,将订单明细映射到OrdersDetails属性,
在订单明细中OrderDetail中添加Items的属性,将订单明细3所对应的商品映射到item
ResultMap和ResultType的总结
resultType:
作用:将查询结果的SQL列名和pojo类的属性名完成一致性的映射
缺点:若SQL结果列名和pojo类属性名不一致,则无法自动完成映射
resultMap:
使用association和Collection完成一对一和一对多高级映射(对结果又特殊要求的映射对象)
association:
作用:将关联查询的信息映射到一个pojo对象中
使用resultType无法将查询结果映射到pojo对象的pojo属性中时,选择resultMap来处理(association)
Collection:
将关联查询映射到一个List集合中
使用resultType无法将查询的结果映射到Pojo对象的List类型的pojo属性中时,使用resultMap的Collection标签来处理
Mybatis中的延时加载
延时加载的使用的配置:
使用懒加载在全局配置XML文件的setting配置
` <settings>
<!--懒加载配置-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>`
resultMap实现高级映射(使用association和Colletion实现一对一和一对多映射)
association和Collection具备延时加载功能
先从单表查询,需要时再从关联表去查询数据,这样能大大提高数据库的性能,单表查询要比多表查询快
在resultMap中使用association为例说明:
使用到association中的select标签加载Statement的ID
<select id="getOrdersById" parameterType="int" resultMap="OrdersResultMap">
select * from orders where id=#{orderId}
</select>
<select id="getOrdersByNumber" parameterType="java.lang.String" resultMap="OrderUserResultMap">
select * from orders where number= #{number};
</select>