01、初始Spring
什么是Spring,它有什么特点?
Spring是一个容器框架,主要负责维护bean与bean之间的关系和生命周期。它具有以下特点:
- 控制反转(IoC):通过依赖注入(DI),减少了组件间的耦合。
- 面向切面编程(AOP):允许将业务逻辑与系统服务(如事务管理和安全性)分离。
- 容器:提供配置和管理对象生命周期的容器,可通过XML、注解或Java代码配置。
- 抽象:为多种编程模型和协议(如JDBC、JMS、JPA)提供抽象层,简化了使用。
- 声明式事务管理:简化了事务操作,并使其与业务代码分离。
- 强大的社区和生态系统:拥有庞大的开发者社区和相关项目(如Spring Boot、Spring Cloud),扩展了Spring的功能。
使用Spring完成第一个案例的步骤
- 创建Maven项目。
- 在
pom.xml
中导入Spring的jar包:<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.9.RELEASE</version> </dependency>
- 配置bean对象的XML文件。
- 编写Spring测试代码:
public class MainApp { public static void main(String[] args) { ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("bean.xml"); HelloWorld helloWorld = (HelloWorld) app.getBean("helloWorld"); String message = helloWorld.getMessage(); System.out.println(message); } }
BeanFactory和IOC容器
BeanFactory
- 底层是继承至
BeanFactory
,负责实例化、配置和组装Bean。
IOC容器
- 利用Java的POJO类和配置元数据生成系统或应用程序。
- 具有依赖注入功能,负责创建对象和建立对象间的依赖。
什么是IOC什么是DI
- IOC(控制反转):将对象的创建和管理控制权从程序代码转移到外部容器。
- DI(依赖注入):在运行时将依赖关系注入到组件中。
BeanFactory和ApplicationContext
BeanFactory
- 概念:最基础的核心接口,提供IOC功能。
- 代码展示:
public static void main(String[] args) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader definitionReader = new XmlBeanDefinitionReader(beanFactory); definitionReader.loadBeanDefinitions("bean.xml"); HelloWorld helloWorld = (HelloWorld) beanFactory.getBean("helloWorld"); System.out.println(helloWorld.getMessage()); }
ApplicationContext
- 概念:BeanFactory的子接口,添加了更多企业级支持功能。
- 代码展示:
public static void main(String[] args) { ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("bean.xml"); HelloWorld helloWorld = (HelloWorld) app.getBean("helloWorld"); System.out.println(helloWorld.getMessage()); }
区别
- 功能范围:
ApplicationContext
提供了更多企业级支持。 - 加载时机:
BeanFactory
采用延时加载,ApplicationContext
采用及时加载。 - 配置和扩展性:
ApplicationContext
提供了更多高级配置选项。 - 事件和消息支持:
ApplicationContext
支持事件发布和监听。 - 国际化支持:
ApplicationContext
支持国际化。 - AOP支持:
ApplicationContext
支持AOP。 - 性能:
BeanFactory
启动速度更快,资源消耗更少。 - 实用性:
ApplicationContext
在大多数企业级应用中推荐使用。
Bean实例化对象的基本流程
- 资源定位:通过资源定位机制找到配置文件。
- Bean定义读取:使用Bean定义读取器读取配置文件中的信息。
- 创建BeanDefinition对象:为每个Bean创建一个
BeanDefinition
对象。 - 注册BeanDefinition:将
BeanDefinition
对象注册到BeanDefinitionRegistry
。 - BeanDefinitionMap存储:存储在
BeanFactory
的BeanDefinitionMap
中。 - 请求Bean实例:应用程序请求容器中的Bean时,查找
BeanDefinitionMap
。 - 实例化Bean:根据
BeanDefinition
中的信息创建一个新的Bean实例。 - 依赖注入:为Bean实例注入依赖。
- Bean生命周期处理:调用Bean的生命周期回调方法。
- 存储到singletonObjects:单例Bean的实例存储在容器的
singletonObjects
缓存中。 - 使用Bean:Bean实例已经准备好,可以被应用程序使用。
- 容器关闭:容器关闭时,调用单例Bean的销毁方法。
Spring框架的体系结构
结构图
模块说明
Spring框架由以下七个模块组成:
- 内核容器:包括
spring-core
、spring-beans
、spring-context
等模块,提供IoC和依赖注入等基本功能。 - AOP和监测器:包括
spring-aop
和spring-aspects
模块,提供面向切面编程的实现。 - 消息传递:
spring-messaging
模块,包含基于消息传递应用服务的关键抽象。 - 数据访问/集成:由JDBC、ORM、OXM、JMS和事务模块组成,提供数据访问和集成的功能。
- Web:由
spring-web
、spring-webmvc
等模块组成,提供面向Web的基础功能、MVC框架、REST web服务实现等。 - 测试:
spring-test
模块,支持使用JUnit或TestNG进行单元测试和集成测试。 - Spring表达式语言:由
spring-expression
模块提供,支持在运行时查询和操作对象的表达式语言。
每个模块都可以单独使用,也可以与其他模块联合实现,共同构成了Spring框架的完整功能。
02、xml方式配置Bean
xml定义bean的相关属性
bean标签常用属性
- id:Bean的唯一标识,IoC容器中bean的id标签不能重复。
- name:用于指定Bean的别名,bean标签的name属性也是不能重复。
- class:定义类的全限定名(包名+类名)。
- singleton:定义Bean是否是Singleton(单例)。
- depends-on:指定当前Bean的依赖Bean。
- init-method:指定当前Bean的初始化方法。
- destroy-method:指定当前Bean的销毁方法。
- lazy-init:指定当前Bean的初始化时间。
- autowire:指定当前Bean的依赖关系的自动注入方式。
- scope:Bean的作用范围。
- dependency-check:指定Bean的依赖检查模式。
autowire 其有五个值
- no:默认值,不进行自动装配。
- byName:根据属性名自动装配。
- byType:通过class指定的类型来自动装配。
- constructor:使用构造函数的参数进行自动装配。
- autodetect:自动进行选择匹配方式。
- default:由上级标签的default-autowire属性确定。
scope值
- singleton:默认值,单例的。
- prototype:多例的。
- request:web项目中,每个HTTP请求创建一个全新的bean实例。
- session:每次会话请求对应一个bean实例。
- globalSession:web项目中,应用在prolet环境。
dependency-check属性 其有四个值
- simple:针对基本类型、字符串、集合进行依赖检查。
- object:对引用对象进行依赖检查。
- all:对基本类型、字符串、集合、引用对象全部进行依赖检查。
- none:不进行任何依赖检查,默认情况。
property标签
- name属性:用于指定属性的名称。
- value属性:用于指定该属性的值。
- ref属性:用于指定该属性的值,用于指定的值是引用对象类型(即其他的Bean)。
Spring实例化对象的几种方式
-
通过构造器实例化
-
通过FactoryBean接口进行实例化
-
通过静态工厂
-
通过实例工厂
-
通过注解
1、在类上添加@Component或其衍生注解(如@Service、@Repository)。
2、配置Spring的组件扫描,指定要扫描的包。
3、启动Spring容器,它会扫描指定的包并查找标记了注解的类。
4、Spring容器会自动实例化这些类。
Spring依赖注入的几种方式
-
set方式注入
-
构造器方式注入
还是以set方式注入的代码为例;使用构造器方式注入的话只需要改变两个地方即可:
一:把set方法改成带参的构造方法;
二:把bean.xml里面的标签换成标签 详解如图所示:
- 自动装配
03、注解
核心注解
- @Component:表示一个类是Spring容器的组件。
- @Service:表示一个类提供业务逻辑服务。
- @Repository:表示一个类提供数据存储服务。
- @Controller:表示一个类是控制器组件,用于处理HTTP请求。
依赖注入注解
- @Autowired:自动注入依赖。
- @Qualifier:与@Autowired配合使用,按名称注入依赖。
- @Resource:类似于@Autowired,但更具体,可以指定名称。
- @Inject:JSR-330标准的注入注解,功能类似于@Autowired。
配置类注解
- @Configuration:表示一个类作为配置类,可以包含多个@Bean定义。
- @Bean:在配置类中,用于定义一个Bean。
- @ComponentScan:指定Spring在初始化时要扫描的路径。
- @PropertySource:指定属性文件的位置。
事务管理注解
- @Transactional:声明一个方法或类需要事务管理。
web相关注解
- @RequestMapping:映射HTTP请求到控制器方法。
- @GetMapping, @PostMapping, @PutMapping, @DeleteMapping:分别用于处理GET, POST, PUT, DELETE类型的HTTP请求。
- @ResponseBody:表示返回的对象是响应体,不是视图名。
- @PathVariable:用于将请求URL中的模板变量映射到功能处理方法的参数上。
- @RequestParam:用于将请求参数区数据映射到功能处理方法的参数上。
- @ModelAttribute:用于将请求参数绑定到对象上。
条件注解
- @Conditional:根据条件判断一个Bean是否需要被创建。
其他注解
- @Aspect:声明一个类为切面类。
- @Pointcut:声明切点。
- @Before, @After, @Around, @AfterReturning, @AfterThrowing:声明通知。
04、面向切面编程
AOP概述
- 定义:AOP是一种通过预编译方式和运行期间动态代理实现程序功能统一维护的技术。
- 作用:允许开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。
- 应用场景:事务管理、记录日志、性能检测、权限控制、缓冲。
Spring AOP术语
- Joinpoint(连接点):程序中可以被拦截的点,例如方法等;
- Pointcut(切入点):指对哪些连接点进行拦截的定义;
- Advice(通知/增强):指拦截到连接点后所要做的事情;
- Introduction (引介):一种特殊的通知,可以在运行期动态地为类添加新的方法或属性。
- Target (目标):被代理的对象。
- Weaving (织入):将增强应用到目标对象,创建代理对象的过程。Spring AOP 支持动态代理织入。
- Proxy (代理):织入增强后的对象,可以是目标对象,也可以是目标对象的代理对象。
- Aspect (切面):切入点和通知的结合。
五种通知类型
- 前置通知(Before):在目标方法之前执行。
- 后置通知(AfterReturning):在目标方法成功执行后执行。
- 返回通知(AfterThrowing):在目标方法抛出异常后执行。
- 最终通知(After):在目标方法执行后无论成功还是抛出异常都会执行。
- 环绕通知(Around):在目标方法执行前后包裹一个通知。
05、事务操作
事务概述
- 定义:事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,要么都失败;
- 特性:原子性、一致性、隔离性、持久性;
事务操作的基本流程
- 开启事务:开始执行一组操作,这些操作要么全部成功,要么全部失败。在数据库中,可以通过SQL语句BEGIN TRANSACTION或START TRANSACTION开启事务。
- 执行操作:在开启的事务中执行一系列数据库操作,例如增删改查等。这些操作会被暂时保存在事务日志中,并不会立即对数据库进行修改。
- 提交事务:如果所有操作都成功执行,则提交事务,将事务日志中的操作应用到数据库,永久性修改数据库数据。在数据库中,可以通过SQL语句COMMIT提交事务。
- 回滚事务:如果在执行操作过程中发生错误,则回滚事务,撤销所有操作,恢复数据库到事务开始之前的状态。在数据库中,可以通过SQL语句ROLLBACK回滚事务。
06、Mybatis和Spring整合Web
整合步骤
- 编写pojo类及映射配置文件
- 编写数据访问接口类
- 编写业务逻辑接口与实现类
- spring配置文件配置
SpringMVC概述
- 定义:SpringMVC是基于Spring的,专门用来做web开发的模块。
- 核心Servlet:DispatcherServlet
Spring Web MVC处理请求的流程
- 发起请求到前端控制器(DispatcherServlet)
- 前端控制器请求HandlerMapping查找
- 处理器映射器HandlerMapping向前端控制器返回Handler
- 前端控制器调用处理器适配器去执行Handler
- 处理器适配器去执行Handler
- Handler执行完成给适配器返回ModelAndView
- 处理器适配器向前端控制器返回ModelAndView
- 前端控制器请求视图解析器去进行视图解析
- 视图解析器向前端控制器返回View
- 前端控制器进行视图渲染
- 前端控制器向用户响应结果
常用组件
- 前端控制器DispatcherServlet
- 作用:接收请求,响应结果,相当于转发器,中央处理器。
- 处理器映射器HandlerMapping
- 作用:根据请求的url查找Handler。
- 处理器适配器HandlerAdapter
- 作用:按照特定规则去执行Handler。
- 处理器Handler
- 作用:编写处理请求的逻辑代码。
- 视图解析器View resolver
- 作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)。
- 视图View
- View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)。
Mybatis
学框架就是学配置
07.Mybatis简介
1.1 MyBatis介绍
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,实质上Mybatis对ibatis进行一些改进。 现在托管到gitHub上,下载:https://github.com/mybatis/mybatis-3/releases
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
Mybatis可以将向 preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象。(输出映射)
1.2 Mybatis框架执行流程
- 读取MyBatis 配置文件mybatis-config.xml,加载数据源、事务等,管理映射文件
- 加载映射文件mapper.xml,用于映射表中列和实体属性的关系
- 定义SQL语句,在上一步的文件中加载。
- 创建会话工厂。(SqlSessionFactory),数据库连接池
- 创建会话(SqlSession),连接对象
- 通过Executor 操作数据库
- 输入参数和输出结果
2. Mybatis 入门程序
2.1 需求
- 根据用户id(主键)查询用户信息
- 根据用户名称模糊查询用户信息
- 添加用户
- 删除用户
- 更新用户
2.2 环境搭建
sql语句:
CREATE TABLE tb_user(
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(20),
password VARCHAR(50),
sex VARCHAR(2),
brithday DATE,
address VARCHAR(200)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
log4j.properties
#在开发阶段使用debug,在生产环境设置为info或者error
log4j.rootLogger=debug,console
#控制台输出
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d %p [%c] - %m%n
添加如下依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.suke</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>mybatis-demo1</artifactId>
<!--添加mybatis的依赖-->
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--mysql的数据库驱动jar-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
</project>
2.3 Mybatis的主配置文件 mybatis-config.xml
mybatis-config.xml主要用来配置mybatis的运行环境,数据源、事务等。
在classpath下创建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>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<!-- 可以配置多个environment -->
<environment id="development">
<!-- 使用jdbc事务管理-->
<transactionManager type="JDBC" />
<!-- 数据库连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/shopdb" />
<property name="username" value="root" />
<property name="password" value="123" />
</dataSource>
</environment>
</environments>
</configuration>
2.4 根据用户id(主键)查询用户信息
编写User实体类
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class User{
//实例类的属性的数据类型写包装类型
private Integer userId;
private String name;
private String password;
private String sex;
//实体类的日期类型一定使用java.util.Date
private Date brithday;
private String address;
}
2.5 sql映射文件
映射文件命名:
User.xml(原始ibatis命名),mapper代理开发映射文件名称叫XXXMapper.xml,比如:UserMapper.xml、ItemsMapper.xml.在映射文件中配置sql语句。
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">
<!-- namespace命名空间,作用就是对sql进行分类管理
注意:使用mapper代理方法开发时,namespace需要特殊设置
-->
<mapper namespace="test">
</mapper>
在UserMapper.xml中添加sql语句
<!-- 根据id获取用户信息 -->
<select id="findUserById" parameterType="int" resultType="org.csmf.mybatis.entity.User">
select * from t_user where id = #{id}
</select>
parameterType:定义输入到sql中的映射类型,#{id}表示使用preparedstatement设置占位符号并将输入变量id传到sql。
resultType:定义结果映射类型。
2.6 将sql映射文件添加到MyBatis主配置文件中
mybatis框架需要加载映射文件,将UserMapper.xml添加到mybatis-config.xml中
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
2.7 测试程序
import java.io.IOException;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.csmf.mybatis.entity.User;
import org.junit.Test;
/**
* 测试使用Mybatis操作数据库
* @author pactera
*
*/
public class UserDaoTest {
@Test
public void testFindUserById(){
InputStream inputStream = null;
try {
//0.加载Mybatis的主配置文件
// Resources.getResourceAsStream()获取classpath下面的资源
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
//1.通过Mybatis的主配置文件得到SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.通过SqlSessionFactory得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//3.通过SqlSession操作数据库
//selectOne():
//第一个参数是Statement的Id也就是sql映射文件中的select,insert,,标签的Id
//第二个参数是输入参数
User user = sqlSession.selectOne("findUserById",2);
//输出
System.out.println(user.getUsername()+"-->"+user.getBrithday());
//4.关闭SqlSession
sqlSession.close();
}
}
2.8 根据用户姓名模糊查询
<!-- 根据姓名模糊查询 -->
<select id="findUserByName" parameterType="string" resultType="org.csmf.mybatis.entity.User">
select * from tb_user where username like #{name}
</select>
2.9 添加用户
<!-- 添加用户 -->
<!--
parameterType: 输入参数为Java对象类型
如果输入参数为java对象类型 #{}中只能写的是Java对象的属性名,不能随意写
注意:如果主键是自增长,不能手动赋值
-->
<insert id="addUser" parameterType="org.csmf.mybatis.entity.User">
insert into tb_user(username,password, sex,birthday, address)
values(#{username},#{password},#{sex},#{brithday},#{address})
</insert>
如果想得到自增列产生的id值,我们可以进行如下配置:
<insert id="insert">
<selectKey resultType="int" order="AFTER" keyProperty="id">
SELECT LAST_INSERT_ID()
</selectKey>
insert into tb_userinfo(name,gender,age,address,email,qq,photo)
values(#{name,jdbcType=VARCHAR},#{gender,jdbcType=VARCHAR},#{age,jdbcType=INTEGER},#{address,jdbcType=VARCHAR}
,#{email,jdbcType=VARCHAR},#{qq,jdbcType=VARCHAR},#{photo,jdbcType=VARCHAR})
</insert>
SELECT LAST_INSERT_ID()
查询生成的id的值,但是需要注意该sql语句必须要与insert的sql语句位于同一次会话中,不然查询不到数据
测试代码
//测试添加用户
@Test
public void testAddUser() throws ParseException{
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUsername("suke");
user.setAddress("深圳");
user.setPassword("123");
user.setSex("男");
//使用SimpleDateFormat把指定格式的字符串转换为日期
// 1992-10-12
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
user.setBrithday(fmt.parse("1992-10-12"));
sqlSession.insert("test.addUser", user);
//提交事务
sqlSession.commit();
//关闭SqlSession
sqlSession.close();
}
2.10 修改用户
<!-- 修改用户 -->
<update id="updateUser" parameterType="org.csmf.mybatis.entity.User">
update t_user set username=#{username},password=#{password},sex=#{sex},
brithday=#{brithday},address=#{address} where id=#{id}
</update>
测试代码:
// 测试修改用户
@Test
public void testUpdateUser() throws ParseException {
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setId(15);
user.setUsername("王五");
user.setAddress("长沙");
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
user.setBrithday(fmt.parse("1991-11-12"));
user.setPassword("123");
user.setSex("男");
//修改
sqlSession.update("updateUser", user);
// 提交事务
sqlSession.commit();
// 关闭SqlSession
sqlSession.close();
}
2.11 总结
2.11.1 #{}
与${}
区别
#{}表示一个占位符号,#{}接收输入参数,类型可以是简单类型,pojo、hashmap。
如果接收简单类型,#{}中可以写成value或其它名称。
#{}接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性…的方式获取对象属性值。
${}
表示一个拼接符号,会引用sql注入,所以不建议使用${}。${}接收输入参数,类型可以是简单类型,pojo、hashmap。
如果接收简单类型,#{}中可以写成value或其它名称。
${}接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性…的方式获取对象属性值。
2.11.2 parameterType和resultType
parameterType:指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在sql中。
resultType:指定输出结果类型,mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。
2.11.2 selectOne和selectList
selectOne查询一条记录
selectList可以查询一条或多条记录。
Mybatis是选择selectOne还是selectList是看返回值来解决,如果返回的一个对象,则使用selectOne,返回一个集合则使用selectList
如果使用selectOne查询多条记录则抛出异常:
3. Mybatis的Dao开发
使用Mybatis开发Dao,通常有两个方法,即原始Dao开发方法和Mapper接口开发方法。
3.1 Mybatis的核心对象
-
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory生产,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是静态代码块中的局部变量
-
SqlSessionFactory
SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。
-
SqlSession
- SqlSession是一个面向用户的接口, sqlSession中定义了数据库操作,默认使用DefaultSqlSession实现类。
- SqlSession的实现类即DefaultSqlSession,此对象中对操作数据库实质上用的是Executor
3. 每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
4. 打开一个 SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。
3.2 传统的DAO开发模式
程序员需要写dao接口和dao实现类。
需要向dao实现类中注入SqlSessionFactory,在方法体内通过SqlSessionFactory创建SqlSession
步骤:
- 编写到接口以及实现类
import java.util.List; public interface IUserDao { /** * 根据ID查询User * @param id 用户Id * @return 用户信息 * @throws Exception */ public User findUserById(int id) throws Exception; /** * 根据用户的名字模糊查询 * @param name 用户的username * @return 一组用户 * @throws Exception */ public List<User> findUserByName(String name) throws Exception; /** * 添加用户 * @param user 用户信息 * @return 包含主键的用户 * @throws Exception */ public User addUser(User user) throws Exception; }
public class UserDaoImpl implements IUserDao { //需要往UserDaoImpl注入SQLSessionFactory private SqlSessionFactory sqlSessionFactory; //通过构造方法注入 public UserDaoImpl(SqlSessionFactory sqlSessionFactory) { this.sqlSessionFactory = sqlSessionFactory; } @Override public User findUserById(int id) throws Exception { /** * 1.得到SqlSession * 2.调用SqlSession对应的方法(selectOne)来操作数据库 * 3.关闭SqlSession * 4.返回结果 */ SqlSession sqlSession = sqlSessionFactory.openSession(); User user = sqlSession.selectOne("test.findUserById", id); sqlSession.close(); return user; } @Override public List<User> findUserByName(String name) throws Exception { /** * 1.得到SqlSession * 2.调用SqlSession对应的方法(selectList)来操作数据库 * 3.关闭SqlSession * 4.返回结果 */ SqlSession sqlSession = sqlSessionFactory.openSession(); List<User> users = sqlSession.selectList("test.findUserByName", "%"+name+"%"); sqlSession.close(); return users; } @Override public void addUser(User user) throws Exception { /** * 1.得到SqlSession * 2.调用SqlSession对应的方法(insert)来操作数据库 * 3.提交事务 * 4.关闭SqlSession */ SqlSession sqlSession = sqlSessionFactory.openSession(); sqlSession.insert("test.addUser", user); sqlSession.commit(); sqlSession.close(); } }
- 编写SQL映射文件
<!-- 根据ID查询 --> <select id="findUserById" parameterType="int" resultType="org.csmf.mybatis.entity.User"> select * from t_user where id = #{id} </select> <!-- 根据姓名模糊查询 --> <select id="findUserByName" parameterType="string" resultType="org.csmf.mybatis.entity.User"> select * from t_user where username like #{name} </select> <!—添加用户--> <insert id="addUser" parameterType="org.csmf.mybatis.entity.User"> <selectKey order="AFTER" resultType="int" keyProperty="id"> select last_insert_id() </selectKey> insert into t_user(username,password,sex,brithday,address) values(#{username},#{password},#{sex},#{brithday},#{address}) </insert>
总结:
原始Dao开发中存在以下问题:
- Dao方法体存在重复代码:通过SqlSessionFactory创建SqlSession,调用SqlSession的数据库操作方法
- 调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不得于开发维护。
3.3 Mapper代理模式
Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper接口开发需要遵循以下规范:
- 在mapper.xml中namespace写的是对应Mapper接口的全限定名
- Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
- Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同
实现:
UserMapper.xml:
UserMapper接口:
测试代码:
import java.io.IOException;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.csmf.mybatis.entity.User;
import org.junit.Before;
import org.junit.Test;
/**
* 测试UserMapper的测试类
* @author pactera
*
*/
public class UserMapperTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp(){
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserById() throws Exception{
SqlSession sqlSession = sqlSessionFactory.openSession();
//创建UserMapper的休息,Mybatis可以自动生成UserMapper接口的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findUserById(2);
System.out.println(user);
}
}
总结:
- selectOne和selectList
动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据mapper接口方法的返回值决定,如果返回list则调用selectList方法,如果返回单个对象则调用selectOne方法。
- namespace
mybatis官方推荐使用mapper代理方法开发mapper接口,程序员不用编写mapper接口实现类,使用mapper代理方法时,输入参数可以使用包装类或map对象,保证dao的通用性。
4. mybatis-config.xml文件的详情
mybatis的全局配置文件SqlMapConfig.xml,配置内容如下:
properties(属性) 加载properties文件
settings(全局配置参数) 缓存
typeAliases(类型别名) 类型别名
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件) 通用Mapper 分页插件
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
4.1 properties属性
在实际开发中,我们通常会将数据库连接参数单独配置在db.properties中,只需要在mybatis-config.xml中加载db.properties的属性值。这样我们在mybatis-config.xml中就不需要对数据库连接参数硬编码。
将数据库连接参数只配置在db.properties中,原因:方便对参数进行统一管理,其它xml可以引用该db.properties。
db.properties文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root
mybatis-config.xml引用properties文件:
<!-- 加载properties文件 -->
<properties resource="db.properties"></properties>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<!-- 可以配置多个environment -->
<environment id="development">
<!-- 使用jdbc事务管理-->
<transactionManager type="JDBC" />
<!-- 数据库连接池-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
4.2 settings全局参数配置
mybatis框架在运行时可以调整一些运行参数。
比如:开启二级缓存、开启延迟加载。
全局参数将会影响mybatis的运行行为。
配置选项 | 描述 | 可选值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局启用或禁用任何Mapper中配置的缓存 | true|false | true |
lazyLoadingEnabled | 全局启用或禁用延迟加载。当启用后,所有的关系都将被延迟加载。可以通过使用特定关系上的fetchType属性来取代该值。 | true|false | false |
aggressiveLazyLoading | 当启用时,具有惰性加载属性的对象将完全在调用任何惰性属性时加载。否则,每个属性都将按需加载。 | true|false | true |
multipleResultSetsEnabled | 允许或不允许从单个语句返回多个结果集(需要兼容的驱动程序) | true|false | true |
useColumnLabe | 使用列标签,而不是列名称。不同的驱动程序在这方面的行为也有所不同。请参考驱动程序文档,或测试这两种模式,以确定驱动程序的行为方式。 | true|false | true |
useGeneratedKeys | 允许JDBC支持已生成的密钥。这时需要一个兼容的驱动程序。这个设置强制生成的键在设置为true时使用,因为一些驱动程序拒绝兼容性但仍然可以工作(例如Derby)。 | true|false | false |
autoMappingBehavior | 指定MyBatis是否以及如何自动将列映射到字段/属性。NONE禁用自动映射。PARTIAL只自动映射简单,非嵌套的结果集。FULL将自动映射任何复杂度的结果集(包含嵌套或非嵌套的)。 | NONE, PARTIAL, FULL | PARTIAL |
defaultExecutorType | 配置默认的执行器(Executor): SIMPLE: 简单执行器 REUSE:重用prepared statement的执行器 BATCH:重用statement并且执行批量更新的执行器 | SIMPLE REUSE BATCH | SIMPLE |
defaultStatementTimeout | 设置数据库超时时间(单位:秒) | 任何正数 | Not Set (null) |
safeRowBoundsEnabled | 允许在嵌套语句上使用行边界。 | true|false | false |
mapUnderscoreToCamelCase | 是否开启, 数据库列:A_COLUMN自动映射为属性:aColumn | true|false | false |
localCacheScope | MyBatis使用本地缓存来提高查询效率,本地缓存默认Scope是:SESSION, 在同一次SESSION期间执行的所有查询都将被缓存. localCacheScope=STATEME:本地会话将仅用于stateme的执行,对同一Sql会话的两个不同调用之间将不共享任何数据 | SESSION|STATEMENT | SESSION |
jdbcTypeForNull | 指定空值的JDBC类型,当没有为参数提供特定的JDBC类型时。一些驱动程序需要指定列JDBC类型,常用的值:NULL,VARCHAR,OTHER | JdbcType enumeration.Most common are: NULL,VARCHAR and OTHER | OTHER |
lazyLoadTriggerMethods | 指定哪个对象的方法会触发延迟加载 | 方法名列表,多个方法名之间使用逗号分割 | equlas, clone, hashCode, toString |
defaultScriptingLanguage | 指定动态SQL生成时默认使用的语言。 | 一个类型的别名或完全限定的类名。 | |
callSettersOnNulls | 指定在检索到的值为空时,是否会调用设置器或映射的put方法。当您依赖于Map.keySet()或空值初始化时,它很有用。请注意原语,如(int、布尔值等)。将不会被设置为null。 | true|false | false |
logPrefix | 指定MyBatis将添加到日志记录器名称中的前缀字符串。 | 任何字符串 | 未设置 |
logImpl | 指定MyBatis应该使用哪些日志记录实现类。如果此设置日志记录类不存在,则将自动发现日志记录实现。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 为设置 |
proxyFactory | 代理工厂指定MyBatis将用于创建能够实现延迟加载功能的对象的代理工具。 | CGLIB|JAVASSIST | CGLIB |
4.3 typeAliases(类型别名)
4.3.1 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 |
4.3.2 自定义别名:
在mybatis-config.xml中配置:
<typeAliases>
<!-- 单个别名定义 -->
<typeAlias alias="user" type="org.csmf.mybatis.entity.User"/>
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
<package name=" org.csmf.mybatis.entity "/>
<package name="其它包"/>
</typeAliases>
使用别名:
4.4 mappers(映射器)
Mapper配置的几种方法:
<mapper resource=" " />
使用相对于类路径的资源
如:
<mapper resource="sqlmap/User.xml" />
<mapper url=" " />
使用完全限定路径
如:
<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />
<mapper class=" " />
使用mapper接口类路径
如:
<mapper class="cn.itcast.mybatis.mapper.UserMapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
<package name=""/>
注册指定包下的所有mapper接口
如:
<package name="org.csmf.mybatis.dao"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
5. SQL映射文件
Mapper.xml映射文件中定义了操作数据库的sql,每个sql是一个statement,映射文件是mybatis的核心。
5.1 parameterType(输入类型)
5.1.1 #{}与${}
#{}实现的是向prepareStatement中的预处理语句中设置参数值,sql语句中#{}表示一个占位符即?。
<!-- 根据id查询用户信息 -->
<select id="findUserById" parameterType="int" resultType="user">
select * from user where id = #{id}
</select>
使用占位符#{}可以有效防止sql注入,在使用时不需要关心参数值的类型,mybatis会自动进行java类型和jdbc类型的转换。#{}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
${}
和#{}
不同,通过${}
可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}
可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}
括号中只能是value。使用${}
不能防止sql注入,但是有时用${}会非常方便,如下的例子:
<!-- 根据名称模糊查询用户信息 -->
<select id="selectUserByName" parameterType="string" resultType="user">
select * from user where username like '%${value}%'
</select>
如果本例子使用#{}则传入的字符串中必须有%号,而%是人为拼接在参数中,显然有点麻烦,如果采用${}在sql中拼接为%的方式则在调用mapper接口传递参数就方便很多。
//如果使用#{}占位符号则必须人为在传参数中加%
List<User> list = userMapper.selectUserByName("%张%");
//如果使用${}原始符号则不用人为在参数中加%
List<User>list = userMapper.selectUserByName("张");
再比如order by排序,如果将列名通过参数传入sql,根据传的列名进行排序,应该写为:
ORDER BY ${columnName}
如果使用#{}将无法实现此功能。
5.1.2 java的对象类型
略,参考前面对user的操作
5.1.3 Java的包装对象类型(一个类包含另外一个类的属性)
开发中通过传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
解决方式: 建议使用自定义的包装类型。在包装类型中将复杂的查询条件包装进去。
- 创建一个类,用来封装查询User的条件:
/**
* 用户条件类
* @author pactera
*
*/
public class UserCondition {
//为了程序的可扩展性,建议重新创建一个类来作为User的查询条件类
private User user;
//...还可以加入其它的条件
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
- UserMapper接口
//根据用户条件查询用户
public List<User> queryByCondition(UserCondition userCondtion) throws Exception
- UserMapper.xml
<!-- 根据查询条件查询用户 -->
<select id="findUserByCondition" resultType="User" parameterType="UserCondition">
select * from t_user where id > #{user.id}
and username like '%${user.username}%'
</select>
5.2 输出映射
5.2.1 resultType
5.2.1.1 输出简单类型
Mapper.xml文件
<!-- 获取用户列表总数 -->
<select id="findUserCount" resultType="int">
select count(1) from user
</select>
Mapper接口:
public int findUserCount() throws Exception;
总结:
查询出来的结果集只有一行且一列,可以使用简单类型进行输出映射
5.2.1.2 输出Java对象类型
不管是输出的单个对象还是一个列表(list中包括对象),在mapper.xml中resultType指定的类型是一样的。都是对象类型
注意:
使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
- 如果查询出来的列名和对象中的属性名全部不一致,没有创建对象。
- 只要查询出来的列名和对象中的属性有一个一致,就会创建对象。
5.2.2 resultMap
在使用resultType作为输出参数,会有以下问题:
- 如果我们的表的列名与对象的属性名不一致,使用resultType时,进行属性映射.
- 如果我们的对象中包含了另外一个对象(一对一,一对多,多对多关联关系),使用resultType进行映射 .
解决上面问题,就需要使用resultMap来作为输出参数.
1. 定义resultMap
用户表:
User类:
在Sql映射文件中,先要手动定义一个resultMap,把对象的属性与表的字段进行映射.
注意: resultType与resultMap不能共存.
6. 动态SQL
mybatis核心 对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装。
6.1 if
<!-- 传递pojo综合查询用户信息 -->
<select id="findUserList" parameterType="user" resultType="user">
select * from user
where 1=1
<if test="id!=null and id!=''">
and id=#{id}
</if>
<if test="username!=null and username!=''">
and username like '%${username}%'
</if>
</select>
6.2 where
上边的sql也可以改为:
<select id="findUserList" parameterType="user" resultType="user">
select * from user
<where>
<if test="id!=null and id!=''">
and id=#{id}
</if>
<if test="username!=null and username!=''">
and username like '%${username}%'
</if>
</where>
</select>
注意:
<where />
可以自动处理第一个and。or
6.3 set
<!-- 修改用户 -->
<update id="updateUser" parameterType="org.csmf.mybatis.entity.User">
update t_user
<set>
<if test="name!=null and name !='' ">
username =#{name},
</if>
<if test="password !=null and password !='' ">
password =#{password},
</if>
<if test="sex!=null and sex !='' ">
sex =#{sex},
</if>
<if test="brithday!=null and brithday !='' ">
brithday =#{brithday},
</if>
<if test="address!=null and address !='' ">
address =#{address},
</if>
</set>
<where>
<if test="id!=null and id!=''">
id = #{id}
</if>
</where>
</update>
<set>
可以处理最后一个逗号
6.4 foreach
向sql传递List,mybatis使用foreach解析.
现在需要通过一组ID查询用户,该sql语句有两种
select * from t_user where id =1 or id=2 or id=3 or id=4;
select * from t_user where id in (1,2,3,4);
那么对应的Sql映射文件中的sql语句也有两种写法:
第一种:
UserMapper接口:
Sql映射文件:
<!-- 根据一组Id查询用户 -->
<select id="findUserByIds" resultType="User" parameterType="QueryVo">
select * from t_user
<where>
<!--select * from t_user where id=? or id=? or =? ... -->
<foreach collection="list" item="id" open="and ( " close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
第二种sql的写法:
<!-- 根据一组Id查询用户 -->
<select id="findUserByIds" resultType="User" parameterType="QueryVo">
select * from t_user
<where>
<!--select * from t_user where id in (? ,?, ?...) -->
<foreach collection="list" item="id" open="and id in ( " close=")" separator=",">
#{id}
</foreach>
</where>
</select>
注意:
<foreach>
标签中collection属性不是写参数名,而是写list/array: List集合的参数写: list, 数组类型的参数写: array
6.5 sql片段
Sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的,如下:
<!--综合查询用户信息 -->
<select id="findUserList" parameterType="user" resultType="user">
select * from user
<where>
<if test="id!=null and id!=''">
and id=#{id}
</if>
<if test="username!=null and username!=''">
and username like '%${username}%'
</if>
</where>
</select>
我们可以把上面的sql的条件单独抽取出来,方便其他的sql复用:
<!--将where条件抽取出来:-->
<sql id="query_user_where">
<if test="id!=null and id!=''">
and id=#{id}
</if>
<if test="username!=null and username!=''">
and username like '%${username}%'
</if>
</sql>
然后在其他需要使用到该sql片段的sql中使用<include>
标签引入
<select id="findUserList" parameterType="user" resultType="user">
select * from user
<where>
<include refid="query_user_where"/>
</where>
</select>
注意:如果引用其它mapper.xml的sql片段,则在引用时需要加上namespace,如下:
<include refid="namespace.sql片段”/>
7. 关联查询
7.1 准备工作:
数据库表:
#订单表:
create table tb_order
(
id INT AUTO_INCREMENT PRIMARY KEY,
userid INT,
createtime DATETIME,
state VARCHAR(20)
) ENGINE=InnoDB AUTO_INCREMENT=1;
#订单详情表
CREATE TABLE tb_orderdetail(
id INT AUTO_INCREMENT PRIMARY KEY,
productId INT,
ordereId INT,
num INT,
price DOUBLE(8,2)
) ENGINE=InnoDB AUTO_INCREMENT=1;
#商品表
CREATE TABLE tb_product (
id INT AUTO_INCREMENT PRIMARY KEY,
name varchar(100),
price DOUBLE(8,2),
description varchar(500)
) ENGINE=InnoDB AUTO_INCREMENT=1 ;
#用户表
CREATE TABLE tb_user(
ID INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(20),
PASSWORD VARCHAR(50),
sex VARCHAR(2),
brithday DATE,
address VARCHAR(200)
) ENGINE=INNODB AUTO_INCREMENT=1 ;
表与表之间的关联:
7.2 一对一查询
需求:
查询订单信息,以及关联的用户信息
注意:因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的订单信息则为一对多查询,因为一个用户可以下多个订单。
实体类:
Sql映射文件:
使用resultMap定义输出参数,
<!-- 定义一个包含用户信息的Order的ResultMap -->
<resultMap type="Order" id="orderMap">
<id column="id" property="id"/>
<result column="createTime" property="createTime"/>
<result column="state" property="state"/>
<!-- 配置关联属性User的信息 -->
<!-- association:用于映射关联单个对象信息
property:关联的属性名,就是将用户对象关联到Order的那个属性
javaType: 属性的类型
-->
<association property="user" javaType="User">
<id column="id" property="id"></id>
<result column="username" property="name"/>
<result column="sex" property="sex"/>
<result column="password" property="password"/>
<result column="address" property="address"/>
<result column="brithday" property="brithday"/>
</association>
</resultMap>
SQL语句
<!-- 查询订单以及该订单所关联的用户 -->
<select id="findOrders" resultMap="orderMap">
SELECT o.* , u.username,u.address FROM tb_order o , tb_user u WHERE o.userId = u.id
</select>
Mapper接口:
测试代码:
7.3 一对多查询
需求:
查询用户,及用户下的所有订单信息
User类:
需要在User类添加一个订单的关联属性,但是一个用户是可以有多个订单的,所以该属性是一个集合属性.
定义一个包含Order信息的User的ResultMap:
<!-- 定义一个包含Order信息的User的ResultMap -->
<resultMap type="User" id="userOrderMap">
<id column="id" property="id"></id>
<result column="username" property="name"/>
<result column="sex" property="sex"/>
<result column="password" property="password"/>
<result column="address" property="address"/>
<result column="brithday" property="brithday"/>
<!-- 订单信息 -->
<!--
collection:对关联查询到的多条记录映射到集合对象中
property:将查询到的多条记录映射到User类的那个属性中
ofType: 指明集合中的元素的类型
-->
<collection property="orders" ofType="Order">
<id column="oid" property="id"></id>
<result column="createTime" property="createTime"/>
<result column="state" property="state"/>
</collection>
</resultMap>
SQL语句定义:
<!-- 查询用户以及该用户的订单信息 -->
<select id="findUserOrder" resultMap="userOrderMap">
select u.* ,o.id oid,o.createtime,o.state from tb_user u , tb_order o where u.id = o.userId
</select>
级联查询的时候,主表和从表有一样的字段名的时候,在mysql上命令查询是没问题的。但在mybatis中主从表需要为相同字段名设置别名
Mapper 接口:
测试代码:
7.4 多对多查询
需求:
查询用户,及用户的订单信息和订单详情信息
SQL语句:
查询主表是:用户表
关联表:由于用户和订单详情表没有直接关联,通过订单进行关联
SELECT u.*, o.id oid , o.createTime, o.state, d.id orderdetailId, d.price,d.num
FROM tb_user u, tb_order o, tb_orderdetail d
WHERE u.id = o.userId AND o.id =d.orderId
映射思路:
将用户信息映射到user中。
在user类中添加订单列表属性List orders,将用户创建的订单映射到orders
在Orders中添加订单明细列表属性Listorderdetials,将订单的明细映射到orderdetials
resultMap:
<!-- 定义一个包含Order信息以及OrderDetail信息的User的ResultMap -->
<resultMap type="User" id="userOrderOrderDetailMap">
<id column="id" property="id"></id>
<result column="username" property="name"/>
<result column="sex" property="sex"/>
<result column="password" property="password"/>
<result column="address" property="address"/>
<result column="brithday" property="brithday"/>
<!-- 订单信息 -->
<!--
collection:对关联查询到的多条记录映射到集合对象中
property:将查询到的多条记录映射到User类的那个属性中
ofType: 指明集合中的元素的类型
-->
<collection property="orders" ofType="Order">
<id column="oid" property="id"></id>
<result column="createTime" property="createTime"/>
<result column="state" property="state"/>
<!-- 订单详情信息 -->
<collection property="orderDetails" ofType="OrderDetail">
<id column="orderDetailId" property="id"></id>
<result column="price" property="price"/>
<result column="num" property="num"/>
</collection>
</collection>
</resultMap>
SQL映射:
<!-- 查询用户以及该用户的订单及订单详情 -->
<select id="findUserOrderOrderDetail" resultMap="userOrderOrderDetailMap">
SELECT u.*, o.id oid , o.createTime, o.state, d.id orderdetailId, d.price,d.num
FROM tb_user u, tb_order o, tb_orderdetail d
WHERE u.id = o.userId AND o.id =d.orderId
</select>
Mapper接口:
测试代码:
8. 缓存
8.1 MyBatis的缓存简介
如下图,是mybatis一级缓存和二级缓存的区别图解:
Mybatis一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
Mybatis二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认开启二级缓存,也可以在setting全局参数中配置开启二级缓存。
8.2 一级缓存
下图是根据id查询用户的一级缓存图解:
一级缓存区域是根据SqlSession为单位划分的。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象,sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。
测试:
@Test
public void testCache1() throws Exception{
SqlSession sqlSession = sqlSessionFactory.openSession();//创建代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//下边查询使用一个SqlSession
//第一次发起请求,查询id为1的用户
User user1 = userMapper.findUserById(1);
System.out.println(user1);
//如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
//更新user1的信息
user1.setUsername("习大大");
userMapper.updateUser(user1);
//执行commit操作去清空缓存
sqlSession.commit();
//第二次发起请求,查询id为1的用户
User user2 = userMapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
分析:
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
8.3 二级缓存
下图是多个sqlSession请求UserMapper的二级缓存图解。
二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域,如果使用mapper代理方法每个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper划分。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象
sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。
开启二级缓存:
在核心配置文件mybatis-config.xml中加入
<setting name="cacheEnabled" value="true"/>
配置项 | 描述 | 允许值 | 默认值 |
---|---|---|---|
cacheEnabled | 对在此配置文件下的所有cache 进行全局性开/关设置。 | true false | true |
要在你的Mapper映射文件中添加一行: <cache />
,表示此mapper开启二级缓存。
实体类要实现序列化
二级缓存需要查询结果映射的pojo对象实现java.io.Serializable接口实现序列化和反序列化操作,注意如果存在父类、成员pojo都需要实现序列化接口。
public class Order implements Serializable
public class User implements Serializable
...
测试
//获取session1
SqlSession session1 = sqlSessionFactory.openSession();
UserMapper userMapper = session1.getMapper(UserMapper.class);
//使用session1执行第一次查询
User user1 = userMapper.findUserById(1);
System.out.println(user1);
//关闭session1
session1.close();
//获取session2
SqlSession session2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
//使用session2执行第二次查询,由于开启了二级缓存这里从缓存中获取数据不再向数据库发出sql
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
//关闭session2
session2.close();
8.4 禁用二级缓存
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
标签:一篇,映射,查询,SSM,SqlSession,user,sql,id
From: https://blog.csdn.net/m0_74064480/article/details/141816779