1.Spring
1.定义
- 1.不同的语境中,
Spring
所代表的含义是不同的- 2.
Spring
包含广义
和狭义
两个语境
1.广义
- 1.广义上的
Spring
泛指以Spring Framework
为核心的Spring
技术栈- 2.
Spring
不是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目组成的成熟技术栈
- 1.
Spring Framework
- 2.
Spring MVC
- 3.
SpringBoot
- 1.
Spring
团队提供的全新框架,为Spring
以及第三方库提供开箱即用的配置,可以简化Spring
应用的搭建及开发过程- 4.
Spring Cloud
- 1.基于
Spring Boot
实现的微服务框架,并不是某一门技术而是一系列微服务解决方案或框架的有序集合- 2.将市面上成熟的、经过验证的微服务框架整合起来,并通过
Spring Boot
进行再封装,屏蔽调其中复杂的配置和实现原理,提供了一套简单易懂、易部署和易维护的分布式系统开发工具包- 5.
Spring Data
- 1.
Spring
技术栈提供的数据访问模块,对JDBC
和ORM
提供了很好的支持- 6.
Spring Security
- 1.身份验证和访问控制框架
2.狭义
- 1.狭义的
Spring
特指Spring Framework
,通常称为Spring
框架,一般作为存放bean
对象的容器- 2.
Spring
框架是一个分层的、面向切面的Java
应用程序的一站式轻量级解决方案,它是Spring
技术栈的核心和基础- 3.
Spring
有两个核心部分:IOC
和AOP
- 1.
IOC
:控制反转(Inverse of Control
) ,指把创建对象过程交给Spring
进行管理- 2.
AOP
:面向切面编(Aspect Oriented Programming
),指封装类的公共行为,将那些与业务无关却被业务模块共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度,同时AOP
还能解决日志、事务、权限
等问题
2.架构图
1.Spring4.0.x版本架构图
- 1.
Test
:简化Spring项目的测试- 2.
Core Container
:核心容器,Spring
作为工厂的实现- 3.
AOP+Aspects
:面向切面编程,是面向对象编程有利的补充,可以非侵入(不需要改写原有代码
)的为代码添加额外功能- 4.
Instrumentation+Messaging
:辅助功能- 5.
Data Access/Integration
:提供数据库访问- 6.
Web
:Spring
对JavaWeb
开发的支持
2.Spring5.0.x版本架构图
- 1.
Core Container
:核心容器,由以下四个模块组成
- 1.
spring-beans
和spring-core
模块:Spring
框架的核心模块,包含了控制反转(Inversion of Control,IOC
)和依赖注入(Dependency Injection,DI
)- 2.
spring-context
模块:构架于核心模块之上,扩展了BeanFactory
并为其添加Bean
生命周期- 3.
spring-expression
模块:统一表达式语言EL
的扩展模块,可以查询、管理运行中的对象,同时也可以调用对象方法、操作数组、集合等- 2.
AOP
:面向切面编程
- 1.
spring-aop
模块:Spring
的另一个核心模块,是AOP
主要的实现模块,提供了面向切面编程实现- 2.
spring-aspects
模块:集成自AspectJ
框架,主要是为AOP
提供多种实现方法- 3.
spring-instrument
模块:基于JAVA SE
中的java.lang.instrument
进行设计,算是AOP
的一个支援模块,主要作用是在JVM
启用时生成一个代理类
,开发者通过代理类在运行时修改类的字节从而改变一个类的功能,实现AOP
的功能- 4.
spring-messaging
模块:报文发送模块,主要职责是为Spring
框架集成一些基础的报文传送- 3.
Data Access
:数据访问集成,由以下五个模块组成
- 1.
spring-jdbc
模块:Spring
提供的JDBC
抽象框架的主要实现模块,用于简化Spring JDBC
- 2.
spring-tx
模块:Spring JDBC
事务控制实现模块,对事务进行封装从而通过AOP
配置可以将事务灵活的配置在任何一层- 3.
spring-orm
模块:ORM
框架支持模块,用于资源管理、数据访问对象(DAO
)的实现- 4.
spring-jms
模块:指Java
消息服务,提供一套消息生产者、消息消费者
模板,JMS
用于在两个应用程序之间或分布式系统中发送消息,进行异步通信- 5.
spring-oxm
模块:提供一个抽象层以支撑OXM
(Object-To-XML-Mapping
),用于将Java
对象映射成XML
数据或将XML
数据映射成Java
对象- 4.
Web
:JavaWeb
编程模块,由以下四个模块组成
- 1.
spring-web
模块:为Spring
提供基础Web
支持,主要建立于核心容器之上通过Servlet
或Listeners
来初始化IOC
容器- 2.
spring-webmvc
模块: 实现Spring MVC
(model-view-Controller
)的Web
应用- 3.
spring-websocket
模块:提供了简单的接口,只要实现响应的接口就可以快速的搭建WebSocket Server
从而实现双向通讯- 4.s
pring-webflux
模块:非堵塞函数式Reactive Web
框架,用来建立异步的,非阻塞,事件驱动的服务- 5.
Test
:即spring-test
模块,主要为测试提供支持,Spring
支持Junit
测试框架- 6.
Spirng
各模块之间的依赖关系
3.使用步骤
1.搭建开发环境
1.新建Maven项目
- 1.参考
ShardingSphere
文章的创建Maven项目
2.导入依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency>
- 1.
pom.xml
文件中导入需要用到的Spring
核心依赖- 2.根据上述
Spring
模块关系依赖图可知导入spring-context
模块即可,其包含Spring
常用核心依赖
3.创建配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
- 1.创建
applicationContext.xml
配置文件,一般放在resources
根目录下
2.创建实体类
package domains; import lombok.Data; @Data public class Person { private String name; private Integer age; private String sex; private Double weight; private Double height; private String phone; private Animal cat; }
package domains; import lombok.Data; @Data public class Animal { private String name; }
3.将对象交给Spring工厂管理
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 通过bean标签将对象交给Spring工厂管理 id: 唯一标识 class: 类的全限定名(包名+类名) --> <bean id="person" class="domains.Person"> <property name="name" value="李四"></property> <property name="age" value="18"></property> <property name="sex" value="男"></property> <property name="height" value="175"></property> <property name="weight" value="62"></property> <property name="phone" value="13465034256"></property> <!-- <property name="cat" ref="animal"></property>--> <property name="cat"> <ref bean="animal"/> </property> </bean> <bean id="animal" class="domains.Animal"> <property name="name" value="喵喵"/> </bean> </beans>
- 1.配置文件中通过
bean
标签将对象交给Spring
工厂管理- 2.声明的同时可以注入属性值,不同类型的属性有不同的注入方式,参考下述
注入方式
4.使用Spring工厂获取对象
package controller; import domains.Person; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTest { public static void main(String[] args) { // 从类路径中获取上下文定义文件(独立的XML应用程序上下文) ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Person person = (Person) context.getBean("person"); System.out.println(person); } }
- 1.通过配置文件
applicationContext.xml
获取到ClassPathXmlApplicationContext
对象- 2.通过该对象的
getBean()
方法获取到相应对象- 3.注意:
applicationContext.xml
是独立的XML
应用程序上下文,而ClassPathXmlApplicationContext
通过类路径获取到该上下文定义文件
4.注入方式
1.Set注入
- 1.配置文件的
bean
标签中定义property
子标签,并在property
子标签中设置属性的默认值- 2.该注入方式本质通过
无参构造
创建对象,并调用属性的set()
方法进行赋值- 3.注意:使用该方式注入的类中必须要有
无参构造
,属性必须要有set()
方法,否则配置文件
的对应属性会爆红出错
1.属性是基本类型+String类型
<bean id="person" class="domains.Person" scope="prototype"> <property name="name"><value>李四</value></property> <property name="age" value="18"/> <property name="sex" value="男"></property> </bean>
- 1.直接使用
property
子标签即可,使用name
指定属性名,使用value
指定属性值,并且有以上三种
不同的写法
2.属性是对象(自定义类型属性)
<bean id="person" class="domains.Person" scope="prototype"> <property name="name"><value>李四</value></property> <property name="age" value="18"/> <property name="sex" value="男"></property> <!-- <property name="cat" ref="animal"></property>--> <property name="cat"> <ref bean="animal"/> </property> </bean> <bean id="animal" class="domains.Animal"> <property name="name" value="喵喵"/> </bean>
- 1.首先需要先声明引用的对象,然后通过
property
子标签即可,有以上两种
方式
- 1.只使用
property
子标签,通过name
指定属性名,通过ref
指定声明对象的唯一标记id
- 2.先使用
property
子标签,通过name
指定属性名,再使用ref
标签,通过bean
指定声明对象的唯一标记id
3.属性是数组,List或Set集合
<bean id="testArray" class="domains.TestArray"> <property name="ids"> <array> <value>1</value> <value>2</value> <value>3</value> <value>3</value> </array> </property> </bean> <bean id="testList" class="domains.TestList"> <property name="ids"> <list> <value>1</value> <value>2</value> <value>3</value> <value>3</value> </list> </property> </bean> <bean id="testSet" class="domains.TestSet"> <property name="ids"> <set> <value>1</value> <value>2</value> <value>3</value> <value>3</value> </set> </property> </bean> <bean id="testArrayObject" class="domains.TestArrayObject"> <property name="persons"> <array> <ref bean="person" /> <ref bean="person" /> </array> </property> </bean> <bean id="testListObject" class="domains.TestListObject"> <property name="persons"> <list> <ref bean="person"/> <ref bean="person"/> </list> </property> </bean> <bean id="testSetObject" class="domains.TestSetObject"> <property name="persons"> <set> <ref bean="person"/> <ref bean="person"/> </set> </property> </bean>
- 1.以上三种属性都是在
property
子标签中嵌套不同的子标签
- 1.数组使用
array
子标签- 2.
List
集合使用list
子标签- 3.
Set
集合使用set
子标签- 2.如果属性是基本类型或
String
类型,可以在相应子标签中使用value
子标签进行赋值- 3.如果属性是引用类型,则先创建引用类型对象的声明,然后通过
ref
子标签的bean
属性指定声明对象的唯一标记id
- 4.注意:
array
,list
,set
子标签可以互换,但set
子标签会对结果进行去重操作
,所以一般见名知意,各自使用各自的对应的标签
4.属性是Map集合
<bean id="testMap" class="domains.TestMap"> <property name="ids"> <map> <entry key="1" value="李四"/> <entry key="1" value="王五"/> <entry key="2" value="赵六"/> </map> </property> <property name="personMap"> <map> <entry key-ref="person" value-ref="person"/> <entry key-ref="person" value-ref="person"/> </map> </property> </bean> <bean id="testProperties" class="domains.TestProperties"> <property name="ids"> <props> <prop key="1">李四</prop> <prop key="1">王五</prop> <prop key="2">赵六</prop> <prop key="3">骑士</prop> </props> </property> </bean>
- 1.普通的
Map
集合使用的是map
子标签,properties
使用的是props
子标签- 2.
map
子标签中嵌套的entry
子标签
- 1.如果
Map
集合中存放的是基本数据类型和String
类型,则直接使用key
指定键,使用value
指定值- 2.如果
Map
集合中存放的是引用数据类型,则需要先创建引用类型对象的声明,然后通过key-ref
属性指定声明对象的唯一标记id
作为键,通过value-ref
属性指定声明对象的唯一标记id
作为值- 3.
Properties
是特殊的Map
集合需要使用特殊的props
和prop
搭配使用- 4.注意
- 1.
Map
中的键如果重复则前者的值会被后者的值覆盖
- 2.
Properties
的输出结果与bean
赋值的顺序相反但其余同Map
一致
2.构造注入
<bean id="user" class="domains.User"> <!--一个constructor-arg代表构造函数中的一个参数,可以使用name指定名称--> <constructor-arg name="name" value="李四"/> <constructor-arg name="age" value="18"/> <constructor-arg value="男" type="java.lang.String"/> <constructor-arg ref="animal" index="3"/> </bean>
- 1.配置文件的
bean
标签中定义constructor-arg
子标签,并在constructor-arg
子标签中设置属性的默认值- 2.一个
constructor-arg
子标签代表构造函数中的一个参数
- 1.使用
name
指定参数名- 2.使用
value
赋值- 3.使用
tepe
指定类型- 4.使用
index
指定在参数中的位置,注意不存在参数类型相同,顺序相同的两个方法,因为这是同一个方法
- 5.使用
ref
指定声明对象的唯一标记id
- 3.该注入方式本质通过调用
不同的构造方法
创建对象,并通过该构造方法注入默认值- 4.注意:使用该方式注入的类中必须要有
对应的构造方法
,否则配置文件
的对应属性会爆红出错
1.赋值为null
<bean id="user" class="domains.User"> <!--一个constructor-arg代表构造函数中的一个参数,可以使用name制定名称--> <constructor-arg name="name"><null></null></constructor-arg> <constructor-arg name="age" ><null></null></constructor-arg> <constructor-arg value="男" type="java.lang.String"/> <constructor-arg ref="animal" index="3"/> </bean>
- 1.构造注入
注入空值
需要使用null
子标签,不能直接使用value=null
2.内部bean
<bean id="person" class="domains.Person" scope="prototype"> <property name="name"><value>李四</value></property> <property name="age" value="18"/> <property name="sex" value="男"></property> <property name="cat"> <bean class="domains.Animal"> <property name="name" value="喵喵"/> </bean> </property> </bean>
- 1.当要求一个
bean
对象不能被其他类获取,只能被当前bean
使用时,可以注入为内部bean
- 2.内部
bean
不需要id
,因为其只能跟随外部bean
一起获取,不能被其他bean
获取,其他和正常的bean
一致
5.代理
1.代理模式
- 产出者:只完成根本需求
- 使用者:需要使用根本需求+额外服务
- 代理类:只有额外服务
- 代理类完成额外的功能,根本功能还是交给基本类,使用类使用代理类,代理类一般使用proxy
1.静态代理
- 代理类必须跟原始类实现同一个接口或者继承同一个父类
- 代理类中只提供额外功能,核心功能在代理中以调用原始类的原始方式的形式出现
- 调用者在使用方法时,要创建代理类的对象,调用者只和代理类打交道
package service;
public interface PersonService {
void insert();
}
package service.impl;
import service.PersonService;
public class PersonServiceImpl implements PersonService {
public void insert() {
System.out.println("调用了insert方法");
}
}
package service.proxy;
import service.PersonService;
import service.impl.PersonServiceImpl;
public class PersonServiceProxy implements PersonService {
private PersonServiceImpl ps = new PersonServiceImpl();
public void insert() {
System.out.println("设置手动提交事务");
try {
ps.insert();
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
throw new RuntimeException();
}
}
}
2.动态代理
- 静态代理问题:
- 1.随着代理的增多,代理类也会逐步增加,代理类代理过于冗长,不利于代理的维护
- 2.多个原始类会使用同一个代理类的增强,静态代理中,原始类每有一个方法,代理类就得写一个方法,无形中也会出现代码冗余的情况
- 解决方法:Spring的动态代理
- 动态代理:代理类不需要手动编写了,当你提供原始类,以及增强代码之后,Spring会自动生成代理类
- 好处:提高开发效率,降低了冗余代理
1.开发步骤
- 1.导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
- 2.编写原始类,交给Spring工厂管理
package service;
public interface UserService {
void selectAll();
}
package service.impl;
import service.UserService;
public class UserServiceImpl implements UserService {
public void selectAll() {
System.out.println("selectAll方法被调用");
}
}
- 3.写增强类,交给Spring工厂管理
package advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class MyBeforeAdvice implements MethodBeforeAdvice {
//在原始方法之前执行的增强
/**
*
* @param method 被增强的原始方法对象
* @param args 原始方法的参数
* @param target 返回值
* @throws Throwable
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("myBeforeAdvice do ....");
System.out.println(method);
System.out.println(args);
System.out.println(target);
}
}
- 4.配置切入点
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--原始类-->
<bean id="userServiceImpl" class="service.impl.UserServiceImpl"/>
<!--增强类-->
<bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/>
<aop:config>
<!--编织切入点
id:自定义
expression:指明原始类需要增强的方法是谁
-->
<aop:pointcut id="userServiceAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<!--组装:将增强功能与被增强方法组装
advice-ref:增强功能的类的bean的id
pointcut-ref:被增强的切入点id
-->
<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="userServiceAdvice" />
</aop:config>
</beans>
myBeforeAdvice do ....
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@7d68ef40
service.impl.UserServiceImpl@5b0abc94
selectAll方法被调用
- 注意context中包含aop组件,所以不需要再导入,不过需要导入aspectj,用于实现AOP做切入点表达式、aop相关注解。而且使用aop在xml中需要导入aop的命名空间,否则会出现没有aop:config的声明错误,xmlns:aop=“http://www.springframework.org/schema/aop”,http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
- 且使用getClass获取对象的类对象后发现该类为代理类class com.sun.proxy.$Proxy3,本质和静态代理一样,只不过是由spring帮我们生成
1.通知(advice)
- 通知(功能增强):为目标方法添加额外功能
- 根据通知在原始方法添加的位置:前置通知,后置通知,异常通知,以及环绕通知
1.前置通知
- 通知会在原始方法之前执行,实现类MethodBeforeAdvice
2.后置通知
- 通知会在原始方法之后执行,实现类AfterReturningAdvice
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userServiceImpl = (UserService) context.getBean("userServiceImpl");
userServiceImpl.selectAll();
System.out.println(userServiceImpl.getClass());
/**
myBeforeAdvice do ....
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@25b485ba
service.impl.UserServiceImpl@2b546384
selectAll方法被调用
MyAfterAdvice do ....
null
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@25b485ba
service.impl.UserServiceImpl@2b546384
class com.sun.proxy.$Proxy3
*/
<!--原始类-->
<bean id="userServiceImpl" class="service.impl.UserServiceImpl"/>
<!--前置增强类-->
<bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/>
<!--后置增强类-->
<bean id="myAfterAdvice" class="advice.MyAfterAdvice"/>
<aop:config>
<!--编织切入点
id:自定义
expression:指明原始类需要增强的方法是谁
-->
<aop:pointcut id="userServiceBeforeAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:pointcut id="userServiceAfterAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<!--组装:将增强功能与被增强方法组装
advice-ref:增强功能的类的bean的id
pointcut-ref:被增强的切入点id
-->
<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="userServiceBeforeAdvice" />
<aop:advisor advice-ref="myAfterAdvice" pointcut-ref="userServiceAfterAdvice"/>
</aop:config>
3.异常通知
- 会在原始方法抛出异常时执行,实现接口ThrowsAdvice
package service.impl;
import service.UserService;
import java.util.Random;
public class UserServiceImpl implements UserService {
public void selectAll() {
Random random = new Random();
int i = random.nextInt();
if (i % 2 == 0) {
throw new RuntimeException("随机数异常");
}
System.out.println("selectAll方法被调用");
}
}
package advice;
import org.springframework.aop.ThrowsAdvice;
import java.lang.reflect.Method;
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] objects, Object o, Exception e){
System.out.println(method);
System.out.println(objects);
System.out.println(o);
System.out.println(e);
System.out.println("MyThrowsAdvice do ...");
}
}
<!--原始类-->
<bean id="userServiceImpl" class="service.impl.UserServiceImpl"/>
<!--前置增强类-->
<bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/>
<!--后置增强类-->
<bean id="myAfterAdvice" class="advice.MyAfterAdvice"/>
<!--异常增强类-->
<bean id="myThrowsAdvice" class="advice.MyThrowsAdvice"/>
<aop:config>
<!--编织切入点
id:自定义
expression:指明原始类需要增强的方法是谁
-->
<aop:pointcut id="userServiceBeforeAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:pointcut id="userServiceAfterAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:pointcut id="userServiceThrowsAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<!--组装:将增强功能与被增强方法组装
advice-ref:增强功能的类的bean的id
pointcut-ref:被增强的切入点id
-->
<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="userServiceBeforeAdvice" />
<aop:advisor advice-ref="myAfterAdvice" pointcut-ref="userServiceAfterAdvice"/>
<aop:advisor advice-ref="myThrowsAdvice" pointcut-ref="userServiceThrowsAdvice"/>
</aop:config>
E:\JDK\bin\java.exe "-javaagent:E:\IDEA\IntelliJ IDEA 2020.2.1\lib\idea_rt.jar=61207:E:\IDEA\IntelliJ IDEA 2020.2.1\bin" -Dfile.encoding=UTF-8 -classpath E:\JDK\jre\lib\charsets.jar;E:\JDK\jre\lib\deploy.jar;E:\JDK\jre\lib\ext\access-bridge-64.jar;E:\JDK\jre\lib\ext\cldrdata.jar;E:\JDK\jre\lib\ext\dnsns.jar;E:\JDK\jre\lib\ext\jaccess.jar;E:\JDK\jre\lib\ext\jfxrt.jar;E:\JDK\jre\lib\ext\localedata.jar;E:\JDK\jre\lib\ext\nashorn.jar;E:\JDK\jre\lib\ext\sunec.jar;E:\JDK\jre\lib\ext\sunjce_provider.jar;E:\JDK\jre\lib\ext\sunmscapi.jar;E:\JDK\jre\lib\ext\sunpkcs11.jar;E:\JDK\jre\lib\ext\zipfs.jar;E:\JDK\jre\lib\javaws.jar;E:\JDK\jre\lib\jce.jar;E:\JDK\jre\lib\jfr.jar;E:\JDK\jre\lib\jfxswt.jar;E:\JDK\jre\lib\jsse.jar;E:\JDK\jre\lib\management-agent.jar;E:\JDK\jre\lib\plugin.jar;E:\JDK\jre\lib\resources.jar;E:\JDK\jre\lib\rt.jar;E:\MySelfProject\Spring\target\classes;C:\Users\32929\.m2\repository\org\projectlombok\lombok\1.16.10\lombok-1.16.10.jar;C:\Users\32929\.m2\repository\org\springframework\spring-context\5.2.8.RELEASE\spring-context-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-aop\5.2.8.RELEASE\spring-aop-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-beans\5.2.8.RELEASE\spring-beans-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-core\5.2.8.RELEASE\spring-core-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-jcl\5.2.8.RELEASE\spring-jcl-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\springframework\spring-expression\5.2.8.RELEASE\spring-expression-5.2.8.RELEASE.jar;C:\Users\32929\.m2\repository\org\aspectj\aspectjweaver\1.9.5\aspectjweaver-1.9.5.jar controller.SpringTest
myBeforeAdvice do ....
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@12d3a4e9
service.impl.UserServiceImpl@240237d2
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@12d3a4e9
service.impl.UserServiceImpl@240237d2
java.lang.RuntimeException: 随机数异常
MyThrowsAdvice do ...
Exception in thread "main" java.lang.RuntimeException: 随机数异常
4.环绕增强
- 会在原始方法的前,后以及发生异常时执行,实现接口MethodInterceptor
package advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAroundAdvice implements MethodInterceptor {
//invocation :方法调用者,用户调用原始方法的动作
public Object invoke(MethodInvocation invocation) throws Throwable {
Object proceed = null;
try{
//前置
System.out.println("这是环绕增强的前置增强");
//执行原始方法,返回值为原始方法的返回值
proceed = invocation.proceed();
//后置
System.out.println("这是环绕增强的后置增强");
} catch (Throwable throwable) {
System.out.println("这是环绕增强的异常增强");
} finally {
System.out.println("这是环绕增强的最终增强");
}
return proceed;
}
}
<!--原始类-->
<bean id="userServiceImpl" class="service.impl.UserServiceImpl"/>
<!--前置增强类-->
<bean id="myBeforeAdvice" class="advice.MyBeforeAdvice"/>
<!--后置增强类-->
<bean id="myAfterAdvice" class="advice.MyAfterAdvice"/>
<!--异常增强类-->
<bean id="myThrowsAdvice" class="advice.MyThrowsAdvice"/>
<!--环绕增强类-->
<bean id="myAroundAdvice" class="advice.MyAroundAdvice"/>
<aop:config>
<!--编织切入点
id:自定义
expression:指明原始类需要增强的方法是谁
-->
<aop:pointcut id="userServiceBeforeAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:pointcut id="userServiceAfterAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:pointcut id="userServiceThrowsAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<aop:pointcut id="userServiceAroundAdvice" expression="execution(void service.impl.UserServiceImpl.selectAll())"/>
<!--组装:将增强功能与被增强方法组装
advice-ref:增强功能的类的bean的id
pointcut-ref:被增强的切入点id
-->
<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="userServiceBeforeAdvice" />
<aop:advisor advice-ref="myAfterAdvice" pointcut-ref="userServiceAfterAdvice"/>
<aop:advisor advice-ref="myThrowsAdvice" pointcut-ref="userServiceThrowsAdvice"/>
<aop:advisor advice-ref="myAroundAdvice" pointcut-ref="userServiceAroundAdvice"/>
</aop:config>
myBeforeAdvice do ....
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@25a65b77
service.impl.UserServiceImpl@2ed0fbae
这是环绕增强的前置增强
selectAll方法被调用
这是环绕增强的后置增强
这是环绕增强的最终增强
MyAfterAdvice do ....
null
public abstract void service.UserService.selectAll()
[Ljava.lang.Object;@25a65b77
service.impl.UserServiceImpl@2ed0fbae
class com.sun.proxy.$Proxy3
- 注意:不论是否发生异常,不会报错,因为会被环绕增强捕获然后输出环绕增强的异常
5.切入点表达式(execution)
- 切入点:需要添加额外功能的方法的位置,通过aop:pointcut标签定义
- 切入点表达式一共有4种写法
1.execution表达式
execution(返回值类型 包名.类名.方法名(参数类型))
- 返回值类型:有返回值时需要指定具体类型,java.util.String。没有返回值时用void,任意返回值时使用*号表示
- 包名,类名,方法名可以用*代替,表示任意
- 参数类型需要指定时可以使用具体类型例Java.util.Integer指定,如果一个任意类型参数可以用*,多个任意类型参数可以用多个星号,任意类型任意个数可以用两个点
<aop:pointcut id="userServiceBeforeAdvice" expression="execution(String service.impl.UserServiceImpl.*(..))"/>
2.args表达式
- 根据形参进行匹配
- args中根据参数类型和顺序进行匹配,如果能匹配上的方法都会被增强
<aop:pointcut id="userServiceBeforeAdvice" expression="args(java.lang.String)"/>
3.within表达式
- 根据全类名进行匹配
<aop:pointcut id="userServiceBeforeAdvice" expression="within(service.impl.UserServiceImpl)"/>
4.@annotion表达式
- 根据自定义注解进行匹配
package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)//限定注解的使用位置,该注解只能用在方法上
@Retention(RetentionPolicy.RUNTIME)//限定注解有效时机,在被注解代理运行时有效
public @interface MyAnnotation {
}
<aop:pointcut id="userServiceBeforeAdvice" expression="@annotation(annotation.MyAnnotation)"/>
public class UserServiceImpl implements UserService {
@MyAnnotation
public void selectAll(String a) {
System.out.println("selectAll方法被调用");
}
}
表达式可以使用逻辑运算符
- 非!
- 和&&
- 或 ||
6.事务控制
- 事务控制的方式:编码式,声明式
1.编码式,将事务控制的代理直接写入service中
JDBC
conn.setAutoCommit(false); 开启手动控制事务
conn.commit() 提交事务
conn.rollback() 回滚
Mybatis
//默认手动提交事务
sqlSession.commit();
sqlSession.rollback();
2.声明式事务控制
- 实现方式:借助Spring的AOP,将事务控制的代码定义成通知,通过AOP编辑进入service的每个方法中
1.导入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
2.定义业务类
- 定义原始的service实现类对象,在实现类中的方法里写核心代码(调用mapper,功能实现的逻辑)
public class UserServiceImpl implements UserService {
private UserMapper userMapper;
public UserMapper getUserMapper() {
return userMapper;
}
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public List<User> selectAll() {
return userMapper.selectAll();
}
}
<!--将userService交给spring-->
<bean id="userService" class="com.baizhi.service.impl.UserServiceImpl">
<property name="userMapper" ref="userMapper"/>
</bean>
3.定义增强类
<!-- 事务增强的bean -->
<bean id="txAdvice" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 填写之前配置的连接池id -->
<property name="dataSource" ref="dataSource"/>
</bean>
这个类存在于框架中,只需要配置bean,不要手动创建该类
4.配置事务增强通知
<!-- 事务增强的通知
id:给该事务增强起名字
transaction-manager:将事务增强的bean交给通知管理
-->
<tx:advice id="txMyAdvice" transaction-manager="txAdvice">
<!--进一步配置事务增强的细节-->
<tx:attributes>
<!-- 所有查询方法添加只读事务
read-only="true" 开启只读事务
-->
<tx:method name="select*" read-only="true"/>
<!-- 增删改方法开启正常事务 -->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
5.编织切点
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* com.baizhi.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txMyAdvice" pointcut-ref="servicePointcut"/>
</aop:config>
3.事务详解
- read-only:表示只读事务(查询时的事务):极大提高查询的效率,只能使用在查询中
- timeout:超时机制,事务提交或者回滚时间超过制定的时长,事务自动提交失败,自动回滚
- 默认时间:属性值设置为-1,跟数据库默认的超时时间一致
- rollback-for和no-rollbacck-for
- rollback-for:当service发生指定的异常时,执行回滚事务
- no-rollback-for:当service发生指定异常时,不回滚
- 如果事务设置为rollback-for,Spring中默认:
- RunTimeException及其子类:执行回滚
- Exception及其子类:不回滚
- 如果日后你想自定义一个异常
- 需要回滚的异常:继承RuntimeException
- 不需要回滚的异常:继承Exception
4.事务的传播机制
- 在企业开发中,会出现一个service方法调用另一个service方法的情况,传播机制定义了,当一个service方法调用另一个service方法时的事务如何处理
- REQUIRED:如果在执行该service之前,已经开启一个事务,那么本service方法就加入该事务,同用同一个事务. 如果在执行该service之前,没有事务开启,那么就开启一个新的事务
- SUPPORTS:如果在执行该service之前,已经开启一个事务么本service方法就加入该事务,同用同一个事务, 如果在执行该service之前,没有事务开启,那么就以无事务状态运行
- REQUIRES_NEW:无论外部是否有事务,都会创建新的事务,挂起旧的事务
5.Spring基于注解的事务开发
<!-- 事务增强的bean -->
<bean id="txAdvice" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 填写之前配置的连接池id -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="txAdvice"/>
@Override
@Transactional(readOnly = true,timeout = 3,propagation = Propagation.REQUIRED)
public List<User> selectAll() {
return userMapper.selectAll();
}
- 传统开发中,注解控制事务使用很少,多数还是使用xml控制事务,但是在SpringBoot中只提供了注解控制事务的方式
7.注解开发
好处:可以简化配置
1.开启Spring注解的包扫描
<!--开启包扫描-->
<context:component-scan base-package="com.baizhi.service,com.baizhi.dao"/>
目前我们的mapper是由mybatis生成的,mapper文件是不需要spring扫描的
如果日后使用的不是mybatis,需要将com.baizhi.dao也被spring扫描
2.为目标类和类的属性添加注解
@Component("abc")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
public UserMapper getUserMapper() {
return userMapper;
}
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public List<User> selectAll() {
return userMapper.selectAll();
}
}
3.注解开发的优化
- @Component替换bean,将类交给Spring工厂管理
- 将@Component细化为三个标签
- @Controller 用在控制器类
- @Service 用在service类
- @Repository 用在dao类上
- 将注解添加在类上之后,会自动生成id
- 注意:
- 1.controller service repository 作用与Component一样,更具有识别性
- 2.Mybiats会帮我们自动实现dao,dao不需要Spring实现的,目前Repository注解不需要使用的
- 3.注解在使用时可以不添加参数,不用手动设置id值,id值默认为类名首字母小写UserServiceImpl ==> getBean(“userServiceImpl”)
- @Autowired 完成属性值的注入的
- 注意:
- 1.@Autowired 根据属性的类型完成注入的,默认从Spring中找与属性类型一直或为其子类的对象
- 2.使用反射注入,不需要属性提供set方法
- @Quaifier(“bean的id值”)
- 与@Autowired一起使用,当有多个相同类型的对象时,帮助@Autowired选择正确的对象
- @Scope 是否单例
- 在不添加此注解时,默认为单例模式
- 如果希望对象为多例 @Scope(“prototype”)
4.Spring实现原理
- 1.按照上述
3.使用步骤
完成项目的搭建后,通过xml
配置文件读取其中的Bean
定义信息,然后将其封装为BeanDefinition
对象- 2.为了方便后续对不同类型定义信息文件进行扩展,定义了一个读取定义信息的接口规范
BeanDefinitionReader
,其有以下两个主要子类
- 1.
XmlBeanDefinitionReader
:读取xml
配置文件- 2.
PropertiesBeanDefinitionReader
:读取properties
配置文件- 3.通过
BeanFactoryPostProcessor
接口可以对BeanFactory
的功能进行增强,完成一些额外的功能
- 1.
PlaceholderConfigurerSupport
:对xml
文件中的占位符进行替换- 2.
ConfigurationClassPostProcessor
:对注解
进行解析- 4.获取
BeanDefinition
对象后可以通过反射对其进行实例化操作,然后进行初始化操作,这里涉及Bean
的生命周期
- 1.实例化
- 2.填充属性
- 3.设置实现Aware接口的属性
- 4.通过
BeanPostProcessor
接口对Bean
进行前置增强- 5.执行
init-method
方法- 6.通过
BeanPostProcessor
接口对Bean
进行后置增强- 7.获取
Bean
的完整对象并使用该对象- 8.销毁该
Bean
对象
1.BeanDefinitionReader
- 1.
xml
和注解
等文件存放bean
的定义信息,用于告知Spring
容器如何生成Bean
对象- 2.通过相应方式读取到
xml
和注解
等文件中存放的bean
的定义信息后生成对应的BeanDefinition
对象然后存放到Spring
容器中,这些BeanDefinition
对象存放在BeanDefinitionMap
类型Map
集合中- 3.
BeanDefinition
是一个接口,定义了一系列对象信息- 4.不同的文件有不同的读取方式,为了方便扩展,定义了一个
BeanDefinitionReader
的接口,用于加载不同的文件中的定义信息
2.BeanFactory
- 1.
BeanFactory
是一个访问Spring Bean
容器的根接口,该接口可以操作Spring
容器中的内容,相当于Spring
容器的入口
1.BeanFactory和FactoryBean
- 1.此处的
BeanFactory
并不是指根接口,而是Bean
对象创建的整体流程,Bean
的生命周期是一个完整的标准化流程,相对比很麻烦,可以通过FactoryBean
接口简单创建一个对象- 2.
FactoryBean
是一个接口,用来创建Bean
对象而不通过BeanFactory
的标准流程,FactoryBean
为创建对象提供了三个抽象方法,可以根据需要创建出具体对象- 3.
BeanFactory
类似一个流水线操作,而FactoryBean
类似定制操作
1.自定义Bean对象
- 1.通过
FactoryBean
接口可以直接创建复杂对象,不需要经过工厂模板的方式
- 1.自定义一个类实现
FactoryBean
接口并实现其中的方法,其中通过getObject()
方法返回需要创建的对象- 2.配置
xml
文件,然后通过Spring
工厂可以获取该对象package domains; import org.springframework.beans.factory.FactoryBean; import java.sql.Connection; import java.sql.DriverManager; import java.util.Collection; public class MyConnectionFactoryBean implements FactoryBean { //获取复杂对象的方式 public Object getObject() throws Exception { Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql?useSSL=false", "root", "root"); return connection; } //返回复杂对象的类型 public Class<?> getObjectType() { return Collection.class; } //返回该对象是否是单例对象,复杂对象不能直接在xml文件中用scope属性设置,需要在这里配置 public boolean isSingleton() { return false; } }
<bean id="connection" class="domains.MyConnectionFactoryBean"/>
2.BeanFactory和ApplicationContext
- 1.
BeanFactory
是一个访问Spring Bean
容器的根接口
- 1.
Spring
容器采用懒加载lazy-load
机制,容器在加载配置文件时并不会立刻创建Java
对象,只有程序中获取该对象时才会创建- 2.默认情况下
Spring
容器创建的对象是单例的,该类型可以通过bean
标签的scope
属性设置
- 1.
singleton
:默认单例,Spring
创建工厂时直接创建- 2.
prototype
:多例,Spring
创建工厂时不会创建对象只有在调用getBean
方法时才开始创建对象(lazy-load
)<bean id="person" class="domains.Person" scope="prototype"> <property name="name" value="李四"></property> <property name="age" value="18"></property> <property name="sex" value="男"></property> <property name="height" value="175"></property> <property name="weight" value="62"></property> <property name="phone" value="13465034256"></property> </bean> public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Object person1 = context.getBean("person"); Object person2 = context.getBean("person"); System.out.println(person1); System.out.println(person2); /** * domains.Person@210366b4 * domains.Person@eec5a4a */ }
- 2.
ApplicationContext
接口继承于BeanFactor
根接口,是对BeanFactory
的扩展,提供更加完善的内容
- 1.
ApplicationContext
接口常用的实现类
- 1.
ClassPathXmlApplicationContext
:加载类路径下指定的XML
配置文件- 2.
FileSystemXmlApplicationContext
:加载完整的文件系统路径下指定的XML
配置文件
3.PostProcessor
- 1.
PostProcessor
类型的接口用于扩展现有的功能,该类型的接口主要有以下两种
- 1.
BeanFactoryPostProcessor
:对BeanFactory
进行扩展- 2.
BeanPostProcessor
:对Bean
进行扩展
- 2.接口和抽象类的区别
- 1.
接口
:自上向下,不需要考虑子类,实现类需要去实现接口的内容- 2.
抽象类
:自下向上,是对子类共性的抽取- 3.具体可参考
JAVA SE
文章中的接口和抽象类的区别
- 3.除了接口可以扩展,
模板方法
设计模式也可以扩展
- 1.
AbstractApplicationContext
类的refresh
中的postProcessBeanFactory
和onRefresh
方法是空的模板方法
- 2.其中
onRefresh
方法在SpringBoot
中进行了扩展,创建了WebServer
从而可以启动Web
项目
1.BeanFactoryPostProcessor
- 1.
BeanFactoryPostProcessor
是对BeanFactory
进行扩展的接口,用于在标准初始化之前修改应用程序上下文的内部Bean工厂
- 2.此时所有的
Bean
定义都已加载,但还没有实例化任何Bean
,该接口允许覆盖或添加属性或抛出异常- 3.该接口是一个函数式接口,其中只有一个抽象方法
postProcessBeanFactory
1.xml文件中占位符替换
- 1.
xml
文件中可能会有其他文件(db.properties
)属性值的引用,因此需要在实例化对象之前进行参数的替换- 2.
BeanFactory
是访问Spring
容器的根接口,可以对容器中的对象进行操作,因此此时可以使用BeanFactoryPostProcesor
接口的实现类PlaceholderConfigurerSupport
完成占位符的替换操作
- 3.占位符替换流程
- 1.执行
AbstractApplicationContext
的refresh
方法- 2.
refresh
方法中的invokeBeanFactoryPostProcessors
是调用各种BeanFactoryPostProcessors
处理器- 3.
Debug
未执行invokeBeanFactoryPostProcessors
方法前,DefaultListableBeanFactory
中的所有的Bean
定义对象都被封装成GenericBeanDefinition
对象然后存放在ConcurrentHashMap
类型的BeanDefinitionMap
中,此时从Debug
中可看到xml
文件中定义的各种对象,且占位符未被替换
- 4.
Debug
执行完invokeBeanFactoryPostProcessors
方法后,占位符被db.properties
中的实际值替换,因此说明替换占位符的功能是由BeanFactoryPostProcessor
扩展接口的实现类PlaceholderConfigurerSupport
完成
2.BeanDefinitionRegistryPostProcessor
- 1.
BeanFactoryPostProcessor
接口有一个子接口BeanDefinitionRegistryPostProcessor
,该接口用于对注解进行解析- 2.因为先有
xml
文件后有注解,注解是在xml
标准解析处理流程上进行扩展
- 3.
ConfigurationClassPostProcessor
是BeanDefinitionRegistryPostProcessor
接口的实现类,其中processConfigBeanDefinitions
方法的parse
方法对注解进行处理
3.自定义BeanFactoryPostProcessor扩展类
- 1.创建一个类实现
BeanFactoryPostProcessor
接口,并且实现postProcessBeanFactory
方法,该方法中编写完扩展内容后将该类通过xml
文件的bean
标签交给Spring
工厂管理即可- 2.执行时会通过
refresh
方法中的invokeBeanFactoryPostProcessors
自动调用该扩展类的postProcessBeanFactory
方法- 3.可以在自定义扩展类上加
@Order
注解指定加载顺序,该功能一般用于二次开发
2.BeanPostProcessor
- 1.
BeanPostProcessor
是对Bean
进行扩展的接口,用于自定义修改Bean
对象,一般用于检查标记接口或用代理包装Bean
- 1.通过标记接口或类似的方式填充
Bean
的处理器将实现postProcessBeforeInitialization
方法- 2.通过代理包装
Bean
的处理器将实现postProcessAfterInitialization
- 3.
ApplicationContext
可以在其Bean
定义中自动检测BeanPostProcessor
,并将这些处理程序应用于随后创建的任何Bean
1.BeanPostProcessor实现AOP动态代理
- 1.
Bean
的生命周期中当完成对象的创建和属性的赋值时,理论上可以直接使用,但是Spring
需要考虑扩展性- 2.因此使用
BeanPostProcessor
接口对Bean
进行扩展,该接口中有两个抽象方法
- 1.前置方法
postProcessBeforeInitialization
- 2.后置方法
postProcessAfterInitialization
,该方法是AOP
的入口- 3.
AOP
是通过代理对象实现,动态代理有两种实现方式
- 1.
JDK
动态代理- 2.
cglib
- 4.
BeanPostProcessor
接口的实现子类中有一个重要抽象类AbstractAutoProxyCreator
,用来创建对应的代理对象,其中代理对象是在后置增强方法中实现- 5.从源码中可以看出
AOP
是IOC
整体流程中的一个扩展点
4.Bean的生命周期
- 1.
生命周期
:从对象的创建使用到销毁的过程
- 1.
实例化
:堆空间中开辟空间,此时对象的属性值一般是默认值,通过反射创建对象,实例化实际调用方法是createBeanInstance
- 2.
初始化
- 1.
属性赋值
:实例化完成后给属性赋值,属性赋值实际调用方式是populateBean
,通过调用属性的set()
方法完成赋值操作- 2.
设置实现Aware接口的属性
:Aware
是一个标记接口,没有任何内容
- 3.调用
BeanPostProcessor
中的前置处理方法postProcessBeforeInitialization
对Bean
对象进行扩展- 4.执行初始化方法
init-method
- 5.调用
BeanPostProcessor
中的后置处理方法postProcessBeforeInitialization
对Bean
对象进行扩展- 6.通过
ClassPathXmlApplicationContext
对象的getBean
方法获取完整对象并使用- 7.通过
ClassPathXmlApplicationContext
对象的close
方法销毁对象
1.Spring中的Bean对象类型
- 1.
Spring
容器中Bean
对象为两类
- 1.
自定义对象
- 2.
容器对象
(BeanFactory
,ApplicationContext
等由容器创建的对象)- 2.自定义类中可以注入容器对象,这些容器对象由
Spring
容器在统一的地方进行统一的注入,因此自定义类需要满足相应标识,即直接或间接实现Aware
接口- 3.例:如果想注入
BeanFactory
容器对象则需要满足BeanFactoryAware
标识,然后在对应的set()
方法中进行赋值
- 4.因此当一个类需要注入容器属性值时可以实现相应的接口,然后通过
invokeAwareMethods
统一调用进行赋值
- 5.
Bean
生命周期中初始化的前两步可以解释为以下内容,方便理解
2.Bean对象的初始化方法
- 1.此处的初始化方法并非指整个
Bean
对象的初始化,而是特指init-method
方法- 2.
Spring
创建对象并为对象填充属性后,可以调用init-metho
的方法进行一些自定义操作
1.修改对象属性的两种方法
- 1.实现
InitializingBean
接口,并重写afterPropertiesSet
方法- 2.
xml
配置中在bean
标签的init-method
属性指定初始化方法的名称- 3.一般两者不会同时出现,如果同时出现则先执行接口的方法后执行
init-method
属性的方法package domains; import org.springframework.beans.factory.InitializingBean; public class Teacher implements InitializingBean { private String id; private String name; private String password; public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "Teacher{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", password='" + password + '\'' + '}'; } public void afterPropertiesSet() throws Exception { //检验对象属性 if(this.name.equals("lisi")){ this.name = "张三"; } System.out.println("这是初始化阶段实现接口的操作"); } public void myInitMethod(){ if(this.name.equals("lisi")){ this.name = "哈哈"; } System.out.println("这是自定义的初始化方法"); } }
<bean id="teacher" class="domains.Teacher" init-method="myInitMethod"> <property name="id" value="1"/> <property name="name" value="lisi"/> <property name="password" value="123456"/> </bean>
这是初始化阶段实现接口的操作 这是自定义的初始化方法 Teacher{id='1', name='张三', password='123456'}
3.Bean对象的销毁方法
- 1.使用完对象关闭工厂
context.closed()
时会执行Bean
对象的销毁方法,此时可以自定义一些销毁操作
- 1.实现
DisposableBean
接口,并重写destroy
方法- 2.
xml
配置中在bean
标签的destroy-method
属性指定销毁方法的名称- 3.一般两者不会同时出现,如果同时出现则先执行接口的方法后执行
destroy-method
属性的方法package domains; import org.springframework.beans.factory.DisposableBean; public class Teacher implements DisposableBean { private String id; private String name; private String password; public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "Teacher{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", password='" + password + '\'' + '}'; } public void destroy() throws Exception { System.out.println("实现接口的销毁阶段"); } public void myDestroyMethod(){ System.out.println("自定义的销毁阶段"); } }
<bean id="teacher" class="domains.Teacher" destroy-method="myDestroyMethod"> <property name="id" value="1"/> <property name="name" value="lisi"/> <property name="password" value="123456"/> </bean>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Teacher teacher = (Teacher) context.getBean("teacher"); System.out.println(teacher); context.close();
4.BeanPostProcoress扩展实例
package domains; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; public class Teacher implements InitializingBean , DisposableBean { private String id; private String name; private String password; public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "Teacher{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", password='" + password + '\'' + '}'; } public void afterPropertiesSet() throws Exception { //检验对象属性 if(this.name.equals("lisi")){ this.name = "张三"; } System.out.println("这是初始化阶段实现接口的操作"); } public void myInitMethod(){ if(this.name.equals("lisi")){ this.name = "哈哈"; } System.out.println("这是自定义的初始化方法"); } public void destroy() throws Exception { System.out.println("实现接口的销毁阶段"); } public void myDestroyMethod(){ System.out.println("自定义的销毁阶段"); } }
package domains; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class MyBeanPostProcessor implements BeanPostProcessor { //在初始化之前执行 //参数含义 //bean:用户要获取的对象 //beanName:对象对应的bean标签的id public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("BeanPostProcessor前置方法+对象:" + bean + " id:" + beanName); return null; } //初始化之后执行 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("BeanPostProcessor后置方法+对象:" + bean + " id:" + beanName); return null; } }
<bean id="teacher" class="domains.Teacher" destroy-method="myDestroyMethod" init-method="myInitMethod"> <property name="id" value="1"/> <property name="name" value="lisi"/> <property name="password" value="123456"/> </bean> <bean class="domains.MyBeanPostProcessor"/>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Teacher teacher = (Teacher) context.getBean("teacher"); System.out.println(teacher); context.close(); /** 结果 * BeanPostProcessor前置方法+对象:Teacher{id='1', name='lisi', password='123456'} id:teacher * 这是初始化阶段实现接口的操作 * 这是自定义的初始化方法 * BeanPostProcessor后置方法+对象:Teacher{id='1', name='张三', password='123456'} id:teacher * Teacher{id='1', name='张三', password='123456'} * 实现接口的销毁阶段 * 自定义的销毁阶段 */
5.IOC 控制反转
- 1.
IOC
(Inversion of Controller
):控制反转,把对象创建和对象之间的调用过程交给Spring
进行管理,降低耦合度
1.IOC底层原理
- 1.调用
AbstractAutowireCapableBeanFactory
类中doCreateBean
方法中createBeanInstance
方法- 2.然后再调用其中的
instantiateBean
方法的instantiate
实例化Bean
- 3.本质是通过反射调用对象的默认无参构造函数
getDeclaredConstructor
,然后通过newInstance
实例化对象
2.DI 依赖注入
- 1.
DI
(Denpendency Injection
):依赖注入,本质是Bean
对象属性注入,该注入方式有两种,可参考上述注入方式
,源码中是在populateBean
方法中完成属性的注入- 2.
依赖关系
:一个对象中需要用到另外一个对象,即对象中存在一个属性,该属性是另外一个类的对象- 3.注意:如果属性类型也是一个对象,且两个对象相互依赖,会导致
循环依赖问题
6.Spring AOP
- 1.AOP:面向切面编程
7.循环依赖问题
- 1.
Spring
创建的Bean
对象默认情况下是单例的,整个容器类有且只有一个该对象- 2.依赖注入时,如果
A
和B
的属性都是对象且彼此依赖,即A
对象依赖B
且B
对象依赖A
则会造成循环依赖的问题- 3.
循环依赖问题
可以采用三级缓存
+提前暴露对象
解决,本质
是因为对象的实例化和初始化可以分开执行- 4.当持有了某一个对象的引用之后,可以在后续步骤给对象进行赋值操作,即先引用后续再进行赋值
1.三级缓存
- 1.
三级缓存结构
存在于DefaultSingletonBeanRegistry
类中,注意三级缓存的HashMap
的值是一个ObjectFactory
函数式接口
- 1.
ObjectFactory
:函数式接口,可以将lambda
表达式作为参数放到方法的实参中,方法执行时并不会实际调用该lambda
表达式,只有在调用getObject
方法时才会调用lambda
表达式- 2.将对象按照状态来分类,可以分为成品和半成品
- 3.一级缓存中存放的是成品对象,二级缓存中存放的是半成品对象,三级缓存中存放的是匿名内部类(
lambda
表达式)- 4.三级缓存的解决流程
- 1.首先创建
A
对象并给A
对象进行实例化操作,此时A
对象是一个半成品对象,存放在二级缓存中- 2.然后给
A
对象的b
属性赋值,先查看容器是否存在B
对象,如果存在则直接赋值,如果没有则创建B
对象并进行实例化,此时B
对象也是一个半成品对象,存放在二级缓存中,此时可以将其赋给A
对象的b
属性,然后将A
的成品对象放到一级缓存中- 3.然后给
B
对象的a
属性赋值,先查看容器是否存在A
对象,因为一级缓存中存在A
的成品对象所以直接赋值完成B
的初始化,此时B
对象是成品,然后将BA
的成品对象放到一级缓存中- 4.此时解决了循环依赖的闭环问题,但是此时既有
A
和B
的成品对象也有半成品对象,为了区分所以引入了三层缓存结构
- 5.三层缓存在进行对象查找的时候是依次查找的,先查找一级缓存,再查找二级缓存,最后查找三级缓存
- 6.两级缓存也能解决循环依赖问题,但是如果存在
AOP
或代理对象时只能使用三级缓存才能解决循环依赖问题
- 1.因为一个容器,不能包含两个同名的对象
- 2.对象创建过程中,原始对象可能需要生成代理对象,如果生成代理对象则需要通过
lambda
表达式在调用时确定是代理对象还是原始对象- 3.普通对象和代理对象不能同时出现在容器中,因此当一个对象代理时就需要使用代理对象覆盖普通对象,实际调用过程中无法确定会何时使用对象,因此需要在调用时才去判断对象是否需要代理对象
- 4.该机制可以使用
lambda
表达式实现,类似一种回调机制,在调用时会去确认是代理还是普通对象,具体方法为getEarlyBeanReference
- 7.缓存的放置时间和删除时间
- 1.
三级缓存
:调用createBeanInstance
实例化对象后通过addSingletonFactory
加入三级缓存- 2.
二级缓存
:通过getSingleton
第一次从三级缓存中确定对象是代理对象还是普通对象,然后将其放入二级缓存同时删除三级缓存- 3.
一级缓存
:生成完整对象后通过addSingleton
将完整对象放入一级缓存并删除二三级缓存
2.提前暴露对象
- 1.提前暴露的是对象的引用,即半成品对象,此时对象还未完成初始化
8.Spring常见面试题
1.循环依赖问题
1.循环依赖如何解决
- 1.循环依赖通过三级缓存和提前暴露对象可以解决
- 2.具体可参考上述内容
2.IOC和DI的理解
- 1.具体可参考上述内容
3.Bean的生命周期
- 1.具体可参考上述内容
4.BeanFactory和FactoryBean有什么区别
- 1.具体可参考上述内容
5.Spring中使用的设计模式
- 1.单例模式:
Bean
创建默认是单例的- 2.工厂模式:
BeanFactory
工厂- 3.模板方法模式:
postProcessBeanFactory
,onRefresh
- 4.策略模式:
XmlBeanDefinitionReader
,PropertiesBeanDefinitionReader
- 5.观察者模式:
listener
- 6.适配器模式:
Adapter
- 7.装饰器模式:
BeanWrapper
- 8.代理模式:
AOP
动态代理
6.Spring AOP
- 1.
AOP
是IOC
的一个扩展功能,先有IOC再有AOP- 2.
AOP
是在BeanPostProcessor
中的后置处理器中对Bean
对象进行的动态代理- 动态代理有两种方式
- 1.
JDK
动态代理- 2.
cglib
动态代理
7.Spring 事务
- 1.
Spring
事务传播(7
种):事务的传播机制指不同方法的嵌套调用中,事务该如何处理,是用同一个事务还是不同的事务,当出现异常是回滚还是提交,两个方法之间相互影响
- 1.
Required
- 2.
Requires_new
- 3.
nested
- 4.
Support
- 5.
Not_Support
- 6.
Never
- 7.
Mandatory
- 2.
Spring
事务的隔离级别
- 1.
Spring
事务的隔离级别和数据库的一致,可参考MySQL
文章中的事务隔离级别
2.Spring MVC
- 1.
Spring MVC
是Spring
为展示层
提供的基于MVC
设计理念的Web
框架,其支持Rest
风格的URL
写法,且更加灵活具有扩展性- 2.
Spring MVC
让普通类成为Controller
控制器且无需继承Servlet
,实现了解耦操作
1.MVC模式
- 1.
Model
:模型层
,提供程序的运行基础业务模型(Dao
封装)- 2.
View
:视图层
,接收服务器的数据并展示到具体页面(html
,jsp
)- 3.
Controller
:控制层
,接收浏览器发送的请求并控制程序的流程(Servlet
封装)
2.传统Servlet
- 1.编码繁琐,开发效率低(必须继承父类
HttpServlet
重写service
方法且一个servlet
类最多提供一个service
方法)- 2.获取请求数据繁琐(手动获取参数并完成类型转换)
- 3.数据传输编码繁琐(手动将数据保存到对应作用域)
- 4.跳转编码方式不统一(请求转发和重定向有不同的方法)
@WebServlet("/xxx") public class XxxServlet extends HttpServlet{ public void service(xxx req,xxx resp){ //1.收参 String username = req.getParameter("username"); ... //2.调用业务层 ... //3.传递参数 req.setAttribute("名字","值") ... //4.跳转 req.getRequestDispatcher("地址").forward(req,resp)//请求转发 resp.sendRedirect("地址")//重定向 } }
3.使用步骤
1.新建web项目
- 1.创建
maven
项目,且使用maven
的webapp
骨架
- 2.删除
pom.xml
配置文件中多余的内容
- 3.手动配置包目录
2.导入依赖
<?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>org.example</groupId> <artifactId>SpringMVC</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>SpringMVC Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.8.RELEASE</version> </dependency> </dependencies> </project>
3.编写springmvc.xml配置文件
- 1.该
springmvc.xml
配置文件位于main/resources
文件夹下- 2.注意:配置文件上需要导入
mvc
和context
命名空间,否则无法识别对应子标签<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 开启springmvc注解 --> <mvc:annotation-driven/> <!-- 配置要扫描注解的包,springmvc针对的是controller层--> <context:component-scan base-package="controller"/> </beans>
4.配置web.xml文件
- 1.前端请求通过
DispatcherServlet
请求分发器分发给不同的处理器,而使用DispatcherServlet
请求分发器需要在web.xml
文件中进行配置
- 1.
servlet-name
子标签:指定DispatcherServlet
请求分发器的名称- 2.
servlet-class
子标签:指定DispatcherServlet
请求分发器的类路径- 3.
url-pattern
子标签:指定请求DispatcherServlet
请求分发器的URL
路径需要满足的模式- 4.
init-param
子标签:指定DispatcherServlet
请求分发器将请求分发给哪些处理器进行处理<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <!--请求分发器:将请求交给springmvc管理--> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--配置springmvc配置文件的位置--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!--Servlet默认第一次访问时创建,配置load-on-startup则会在tomcat启动时提前创建,可以减少一次访问的时间--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <!--配置访问路径的通配符,需要以.do结尾--> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
- 2.
DispatcherServlet
请求分发器的分发流程
- 1.满足对应
URL
模式的请求通过DispatcherServlet
请求分发器分发请求- 2.通过配置文件中指定配置文件路径找到对应的请求处理器
- 3.通过
controller
处理器处理对应的请求
5.安装并配置tomcat
- 1.官网下载
tomcat
压缩包然后解压- 2.配置
idea
的tomcat
环境,即先配置本地tomcat
服务器的地址,然后配置Deployment
应用上下文路径(访问项目路径)
6.编写控制器
package controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class FirstController { @RequestMapping("/hello") public ModelAndView hello(){ //业务 System.out.println("Hello SpringMVC"); //跳转 ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("redirect:/index.jsp"); //转发:forward:/index.jsp //重定向:redirect:/index.jsp //返回值 return modelAndView; } @RequestMapping("/bey") public ModelAndView bey(){ //业务 System.out.println("bey SpringMVC"); //跳转 ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("redirect:/index.jsp"); //转发:forward:/index.jsp //重定向:redirect:/index.jsp //返回值 return modelAndView; } }
7.访问
- 1.
http://localhost:端口号/项目名/requestMapping路径.do
- 2.例:
http://localhost:8080/SpringMVC_war/hello.do
- 3.注意
- 1.重定向会
改变地址栏地址
,而请求转发则不会- 2.访问路径后需要加上
.do
,因为web.xml
中对URL
的模式进行了限制,只要满足了条件才能被DispatcherServlet
请求分发器分发请求
8.整体架构图
4.SpringMVC注解
1.@RequestMapping
- 1.
@RequestMapping
注解:用于指定请求的映射的路径,可以用在类和方法上,其常用属性有以下几种
- 1.
value
:匹配请求访问的路径//请求路径需要包含value的值,且需要满足URL的模式 @RequestMapping(value = "/hello",method = RequestMethod.POST)
- 2.
method
:设定处理的请求方式//get请求:a标签,地址栏输入,表单get提交方法 //post请求:表单post提交方法 @RequestMapping(value = "/hello",method = RequestMethod.POST)
- 2.注意类和方法上的
/
都不会影响访问的路径,因此可省略但建议带上
2.@RequestParam
- 1.
@RequestParam
注解:用于设置请求参数,只能用在方法参数上,其常用属性有以下几种
- 1.
value
:设置参数值的别名,传参时需要使用该别名,否则无法获取- 2.
required
:设置该值是否为必须的- 3.
defaultValue
:设置当该值没有时的默认值@RequestMapping(value = "/insert",method = RequestMethod.GET) public ModelAndView insert(String name, Integer age, @RequestParam(value = "AliasHeight", required = true, defaultValue = "72") Double height){ //... }
3.@RestControllerAdvice
- 1.
@RestControllerAdvice
注解是一个组合注解,由@ControllerAdvice
,@ResponseBody
组成- 2.
@ControllerAdvice
注解:用于统一处理控制器(使用@RequestMapping的控制器的方法
)的全局配置,注解了@RestControllerAdvice
的类的方法上可以使用以下几种注解
- 1.
@ExceptionHandler
:用于指定异常处理方法,当与@RestControllerAdvice
配合使用时,用于全局处理控制器中的异常- 2.
@InitBinder
:用来设置WebDataBinder
,用于自动绑定前台请求参数到Model
中- 3.
@ModelAttribute
:用于绑定键值对到Model
中,当与@ControllerAdvice
配合使用时,可以让全局的@RequestMapping
都能获得在此处设置的键值对- 3.具体可参考
异常及其使用方式
文章中的统一异常处理类
4.@ExceptionHandler
- 1.用于指定异常处理方法,当与
@RestControllerAdvice
配合使用时,用于全局处理控制器中的异常- 2.具体可参考上述
@RestControllerAdvic
注解
5.@PathVariable
- 1.
@PathVariable
注解:用于将URL
中占位符参数绑定到控制器处理方法的入参中
- 1.若方法参数名称和需要绑定的
URL
中变量名称一致时,可以简写@RequestMapping("/getUser/{name}") public User getUser(@PathVariable String name){ return userService.selectUser(name); }
- 2.若方法参数名称和需要绑定的
URL
中变量名称不一致时,则需要指定@RequestMapping("/getUserById/{name}") public User getUser(@PathVariable("name") String userName){ return userService.selectUser(userName); }
7.@RequestBody
- 1.
@RequestBody
注解:将请求报文中的请求体(Json
格式)转换为Java
类型的数据- 2.该注解只能用于标识方法
参数
8.@ResponseBody
- 1.
@ResponseBody
注解:将Java类型的数据转换为响应报文中的响应体(Json
格式)- 2.该注解可以用于标识
控制器方法
或控制器类
,将该方法或类中所有方法的返回值直接作为响应报文的响应体(Json
格式)响应到浏览器
9.@RestController
- 1.
@RestController
注解是一个组合注解,由@ResponseBody
和@Controller
组成- 2.
@Controller
注解:是一种特殊化的@Component
,用于将其交给Spring
容器管理且标记该类是一个控制器
5.执行流程
- 1.项目启动
Tomcat
服务器时首先会加载程序的web.xml
文件- 2.实例并初始化
web.xml
文件中的核心控制器DispatcherServlet
- 3.通过
web.xml
文件中的配置找到并加载springmvc.xml
文件,创建Spring
容器并初始化容器中的对象- 4.客户端浏览器发送请求,首先请求会到达前端控制器
DispatcherServlet
- 5.
DispatcherServlet
根据映射规则找到对应的controller
处理器- 6.执行完
controller
业务代码后会返回页面路径,
视图解析器InternalResourceViewResolver
对其进行解析找到具体的页面- 7.将结果响应到浏览器,并渲染页面
1.核心组件及执行步骤
- 1.核心控制器:
DispatcherServlet
- 1.所有用户请求首先会到达核心控制器
DispatcherServlet
,DispatcherServlet
是整个流程的控制中心- 2.
DispatcherServlet
并不直接处理用户请求,而是将调用不同组件处理不同的功能- 2.三大组件
- 1.处理器映射器:
HandlerMapping
- 1.对请求路径进行解析,实际负责查找
controller
处理器的组件- 2.处理器适配器:
HandlerAdapter
- 1.适配器模式的应用,实际通过
HanderAdapter
对controller
处理器进行执行- 2.通过扩展适配器可以对更多类型的处理器进行执行,不同的参数和不同的返回类型需要不同的适配器
- 3.视图解析器:
ViewResolver
- 1.负责将
controller
处理后的结果生成View
视图- 2.
ViewResolver
根据逻辑视图名解析成物理视图名,即具体的页面地址,再生成View
视图对象- 3.最后对
View
进行渲染,将处理结果通过页面展示给用户- 3.处理器:指开发人员编写的
controller
处理方法- 4.执行步骤
- 1.客户端浏览器请求
Tomcat
服务器,请求首先到达前端控制器DispatcherServlet
- 2.
DispatcherServlet
调用处理器映射器HandlerMapping
去解析请求路径并通过其找到对应controller
处理器,然后返回处理器执行链交给DispatcherServlet
- 3.
DispatcherServlet
调用处理适配器HandlerAdapter
去执行controller
处理器方法,执行结束后返回封装好的ModeAndView
交给DispatcherServlet
- 4.
DispatcherServlet
调用视图解析器ViewResolver
去解析ModelAndView
中的信息并生成View
对象交给DispatcherServlet
- 5.
DispatcherServlet
将View
对象放到视图层进行渲染,最后将视图信息响应给客户端浏览器- 5.注意
- 1.需要在
web.xml
中配置对应的核心控制器DispatcherServlet
以及拦截规则<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <!--请求分发器:将请求交给springmvc管理--> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--配置springmvc配置文件的位置--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!--Servlet默认第一次访问时创建,配置load-on-startup则会在tomcat启动时提前创建--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <!--注意访问路径需要带上.do后缀才能被处理--> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
- 2.需要在对应的
springmvc.xml
中配置上述三大组件以及处理器,其中处理器映射器HandlerMapping
和处理器适配器HandlerAdapter
可以简写<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 配置处理器映射器 --> <!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean> --> <!-- 配置处理器适配器 --> <!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean> --> <!-- 上述可简写为该方式: 开启springmvc注解驱动,自动配置处理器和处理器适配器 --> <mvc:annotation-driven/> <!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--指定路径的前缀和后缀--> <property name="prefix" value="/WEB-INF/view/"></property> <property name="suffix" value=".jsp"></property> </bean> <!-- 配置要扫描注解的包 即处理器 --> <context:component-scan base-package="controller"/> </beans>
6.静态资源放行
- 1.当
url-pattern
标签的拦截路径为/
时会对所有除jsp
页面的所有路径进行拦截- 2.当
jsp
页面中有静态资源访问时也会被拦截导致无法加载,此时需要配置静态资源放行
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 配置处理器映射器 --> <!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean> --> <!-- 配置处理器适配器 --> <!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean> --> <!-- 上述可简写为该方式: 开启springmvc注解驱动,自动配置处理器和处理器适配器 --> <mvc:annotation-driven/> <!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--指定路径的前缀和后缀--> <property name="prefix" value="/WEB-INF/view/"></property> <property name="suffix" value=".jsp"></property> </bean> <!-- 配置要扫描注解的包 即处理器 --> <context:component-scan base-package="controller"/> <!-- 配置静态资源放行 --> <!-- mapping: 请求url中的路径 location: 对应项目目录的资源--> <mvc:resources mapping="/static/**" location="/static/"></mvc:resources> </beans>
7.请求参数类型
1.基本数据包装类+String
- 1.基本数据类型可以直接通过地址栏拼接,通过名称一致的形参自动赋值
- 一般使用基本数据类型包装类,否则如果参数不存在,默认值为null时基本数据类型会报错500
@RequestMapping(value = "/insert",method = RequestMethod.GET) public ModelAndView insert(String name, Integer age, Double height){ //业务 System.out.println("Insert SpringMVC"); System.out.println(name); System.out.println(age); System.out.println(height); //跳转 ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("redirect:/index.jsp"); //转发:forward:/index.jsp //重定向:redirect:/index.jsp //返回值 return modelAndView; }
http://localhost:8080/SpringMVC_war/test/insert.do?name=李四sir&age=18 Insert SpringMVC ??????sir 18 null
2.对象类型
3.数组,List,Set
4.对象集合
5.Map
8.响应参数类型
1.String类型
- 1.如果返回参数类型为
String
类型,则该参数表示要跳转的页面的路径,默认使用请求转发跳转
,即地址栏地址不改变- 2.如果该页面存放在嵌套文件夹下,可以使用
视图解析器
简化该页面路径,即配置页面的前缀
和后缀
,跳转时会在返回路径上自动加上前后缀
- 3.
web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <!--请求分发器:将请求交给springmvc管理--> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--配置springmvc配置文件的位置--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-servlet.xml</param-value> </init-param> <!--Servlet默认第一次访问时创建,配置load-on-startup则会在tomcat启动时提前创建--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
- 4.
springmvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 开启springmvc注解 --> <mvc:annotation-driven/> <!-- 配置要扫描注解的包--> <context:component-scan base-package="controller"/> <!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--指定路径的前缀和后缀--> <property name="prefix" value="/WEB-INF/view/"></property> <property name="suffix" value=".jsp"></property> </bean> </beans>
- 5.
controller
package controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping(value = "/test") public class FirstController { @RequestMapping("/first") public String first(){ //业务 System.out.println("first SpringMVC"); //返回值 return "hello"; } }
- 6.访问路径
- 7.跳转页面
9.Restful风格
- 对应处理器指定请求方式的控制器方法,SpringMVC中提供了@RquestMapping的派生注解
- 处理get请求的映射->@GetMapping
- 处理post请求的映射->@PostMapping
- 处理put请求的映射->@PutMapping
- 处理delete请求的映射->@DeleteMaping
4.@RequestMapping的params属性
- params属性通过请求的请求参数匹配请求映射,不匹配则报400
- params属性是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系,如果有多个参数则必须全部满足才能映射成功
- param:要求请求映射所匹配的请求必须携带param请求参数
- !param:要求请求映射所匹配的请求必须不能携带param请求参数
- param=value:要求请求映射所匹配的请求必须携带param请求参数且param=value
- param!=value:要求请求映射所匹配的请求必须携带param请求参数,但是param!=value
5.@RequestMapping的headers属性
- headers属性通过请求的请求头信息匹配请求映射
- headers属性是一个字符串类型的数组,可以通过四种表达式设置请求头信息和请求映射的匹配关系
- header:要求请求映射所匹配的请求必须携带header请求头信息
- !header:要求请求映射所匹配的请求必须不能携带header请求头信息
- header=value:要求请求映射所匹配的请求必须携带header请求头且header=value
- header!=value:要求请求映射所匹配的请求必须携带header请求头,但是header!=value
SpringMVC支持路径中的占位符(重点)
- 原始方式:/deleteUser?id=1
- rest方式:/deleteUser/1
- SpringMVC路径中的占位符常用于restful风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的vallue属性中通过占位符{xxx}表示传输的数据,再通过@PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参
- 如果value路径中有占位符,则请求路径中也必须有值,需要对应
SpringMVC获取请求参数
1.通过servletAPI获取
- 将HttpServletRequset作为控制器方法的形参,此时HttpServletRequest类型的参数表示封装了当前请求的请求报文的对象
2.通过控制器方法的形参获取请求参数
- 在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,在DispatcherServlet中就会将请求参数赋值给相应的形参
- 对于单个的同名的参数直接赋值给形参,对于多个同名的参数可以使用字符串接收,结果中间会以逗号隔开,也可以通过字符串类型的数组接收
- 如果请求参数和形参名称不一致则不能进行赋值
@RequestParam
- 将请求参数和控制器方法的形参创建映射关系
- RequestParam注解一共有三个属性
- value:指定为形参赋值的请求参数的参数名
- required:设置是否必须传输此参数请求参数,默认值为true
- 若设置为true时,则当前请求必须传输value所指定的请求参数,若没有传输该请求参数,且没有设置defaultValue属性,则页面报错400;若设置为false,则当前请求不是必须传输value所指定的请求参数,若没有传输,则注解所标示的形参的值为null;
- defaultValue:不管required属性值为true或false,当value所指定的请求参数没有传输或传输的值为“”时,则使用默认值为形参赋值
@RequestHeader
- @ReqeustHeader是将请求头信息和控制器方法的形参创建映射关系
- @RequestHeader注解一共有三个属性:value,required,defaultValue,用法同@RequestParam
- 想要获取请求头信息则必须使用该参数
@CookieValue
- CookieValue是将cookie数据和控制器方法的形参创建映射关系
- CookieValue注解一共有三个属性:value,required,defaultValue,用法同@RequestParam
通过POJO获取请求参数
- 可以在控制器方法的形参位置设置一个实体类类型的形参,此时若浏览器传输的请求参数的参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值
解决获取请求参数的乱码问题
- 解决获取请求参数的乱码问题,可以使用SpringMVC提供的编码过滤器CharacterEncodingFilter,但是必须在web.xml中进行注册
- SpringMVC中处理编码的过滤器一定要配置到其他过滤器之前,否则无效
域对象共享数据
- 1.使用servletAPI向request域对象共享数据
- 2.使用ModelAndView向request域对象共享数据
- 3.使用Model向request域对象共享数据
- 4.使用map向requsest
- 5.使用ModelMap向request域对象共享数据
- 6.Model,ModelMap,Map的关系
- Model,ModelMap,Map类型的参数其实本质上都是BingingAwareModelMap类型的
7.向session域共享数据
8.向application域共享数据
SpringMVC的视图
- SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户
- SpringMVC视图的种类很多,默认有转发视图和重定向视图
- 当工厂引入jstl的依赖,默认视图会自动转换为JstlView
- 若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymelead的视图解析器,由此视图解析器解析之后所得到的的是ThymeleafView
ThymeleafView
- 当控制器方法中所设置的视图名称没有任何前缀时,
RESTFul
- REST:Representatation State Transfer,表现层资源状态转移
- 是一种软件架构的风格,一种格式
RESTFul的实现
- 具体说就是HTTP协议里面,四个表示操作方式的动词:GET,POST,PUT,DELETE
- 他们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源
- REST风格提倡URL地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为URL地址的一部分,以保证整体风格的一致性
操作 传统方式 REST风格 查询操作 getUserById?id=1 user/1–>get请求方式 保存操作 saveUser user–>post请求方式 删除操作 deleteUser?id=1 user/1–>delete请求方式 更新操作 updateUser user–>put请求方式
3.Mybatis
1.历史
- 1.MyBatis最初是Apache的一个开源项目iBatis
- 2.2010年6月这个项目由Apache Software Foundation迁移到了Google Code
- 3.随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis,代码于2013年11月迁移到Github
2.定义
- 1.MyBatis 是一个开源、轻量级的数据持久化框架,是 JDBC 和 Hibernate 的替代方案
- 2.MyBatis 内部封装了JDBC,简化了加载驱动、创建连接、创建 statement 等繁杂的过程,开发者只需要关注 SQL 语句本身。
- 3.数据持久化是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中数据模型的统称(例:文件的存储、数据的读取以及对数据表的增删改查等都是数据持久化操作)
- 4.MyBatis 支持自定义 SQL、存储过程以及高级映射,可以在实体类和 SQL 语句之间建立映射关系,是一种半自动化的 ORM 实现。
- 5.ORM(Object Relational Mapping,对象关系映射)是一种数据持久化技术,它在对象模型和关系型数据库之间建立起对应关系,并且提供了一种机制,通过 JavaBean 对象去操作数据库表中的数据。
- 6.MyBatis 的主要思想是将JDBC中的大量 SQL 语句剥离出来,使用 XML 文件或注解的方式实现 SQL 的灵活配置,将 SQL 语句与程序代码分离,在不修改程序代码的情况下,直接在配置文件中修改 SQL 语句。
3.优缺点
1.优点
- 1.MyBatis 免费且开源
- 2.MyBatis 简化JDBC代码,小巧并且简单易学
- 3.MyBatis 将SQL语句与逻辑代码分离,降低耦合度,便于统一管理和优化,提高了代码的可重用性
- 4.MyBatis 提供 XML 标签,支持编写动态 SQL 语句
- 5.MyBatis 提供映射标签,支持对象与数据库的 ORM 字段关系映射。
- 6.MyBatis 可以使用一系列插件,简化开发
2.缺点
- SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
4.适用场景
- 1.对性能要求高,可以使用 JDBC
- 2.业务简单的项目可以使用 Hibernate
- 3.需要灵活的 SQL ,可以使用 MyBatis
- 4.Spring JDBC 可以和 ORM 框架混用
5.MyBatis和Hibernate的区别
- Hibernate 和 MyBatis 都是目前业界中主流的对象关系映射(ORM)框架,它们的主要区别如下
1.优化比较
- 1.Hibernate 使用 HQL(Hibernate Query Language)语句,独立于数据库,不需要编写大量的 SQL就可以完全映射,但会多消耗性能,且开发人员不能自主的进行 SQL 性能优化
- 2.MyBatis 需要手动编写 SQL,所以灵活多变。支持动态 SQL、处理列表、动态生成表名、支持存储过程
2.映射比较
- 1.MyBatis 是一个半自动映射的框架,因为 MyBatis 需要手动匹配 POJO 和 SQL 的映射关系。
- 2.Hibernate 是一个全表映射的框架,只需提供 POJO 和映射关系即可
3.缓存机制比较
- 1.Hibernate 的二级缓存配置在 SessionFactory 生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置缓存
- 2.MyBatis 的二级缓存配置在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制,并且 Mybatis 可以在命名空间中共享相同的缓存配置和实例,通过 Cache-ref 来实现
- 3.Hibernate 对查询对象有着良好的管理机制,用户无需关心 SQL,所以在使用二级缓存时如果出现脏数据,系统会报出错误并提示
- MyBatis 使用二级缓存时需要特别小心,如果不能完全确定数据更新操作的波及范围,应避免 Cache 的盲目使用,否则脏数据的出现会给系统的正常运行带来很大的隐患
4.优缺点比较
Hibernate
- 1.Hibernate 的 DAO 层开发比 MyBatis 简单;Mybatis 需要维护 SQL 和结果映射
- 2.Hibernate 对对象的维护和缓存要比 MyBatis 好,对增删改查的对象的维护要方便
- 3.Hibernate 数据库移植性很好;MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL
- 4.Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳
Mybatis
- 1.MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段。
- 2.MyBatis 容易掌握,而 Hibernate 门槛较高。
6.第一个MyBatis程序
1.新建Maven项目
2.导入架包
<?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>org.example</groupId> <artifactId>Mybatis</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <!--mysql连接,其中Driver就在其中--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> </dependencies> </project>
3.导入配置文件
1.log4j.properties(poperties失败,导入log4j.xml文件)
- 作用:开启log4j日志功能,用于查看运行日志
- 位置:main/resources根目录下
- 内容:
log4j.rootLogger=DEBUG, stdout # SqlMap logging configuration... log4j.logger.com.ibatis=DEBUG log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG log4j.logger.com.ibatis.sqlmap.engine.cache.CacheModel=DEBUG log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientImpl=DEBUG log4j.logger.com.ibatis.sqlmap.engine.builder.xml.SqlMapParser=DEBUG log4j.logger.com.ibatis.common.util.StopWatch=DEBUG log4j.logger.java.sql.Connection=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG log4j.logger.java.sql.ResultSet=DEBUG # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"> <param name="Encoding" value="UTF-8"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m(%F:%L) \n"/> </layout> </appender> <!--范围--> <logger name="java.sql"> <!--级别--> <level value="debug"/> </logger> <logger name="org.apache.ibatis"> <level value="info"/> </logger> <root> <level value="debug"/> <appender-ref ref="STDOUT"/> </root> <!--日志的级别 FATAL(致命) > ERROR(错误) > WARN(警告) > INFO(信息) > DEBUG(调试) --> </log4j:configuration>
2.mybatis-config.xml
- 作用:对Myabtis框架进行基本配置(例:数据库的用户名密码,mapper.xml的位置)
- 位置:main/resources根目录下
- 内容:
<?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> <!-- 标签的顺序需要按如下设置 1.properties 2.settings 3.typeAliases 4.typeHandlers 5.objectFactory 6.objectWrapperFactory 7.reflectorFactory 8.plugins 9.environments 10.databaseIdProvider 11.mappers --> <!-- environments标签配置数据库环境 1.environments标签下可以配置多个environment标签 2.每一个environment标签相当于一个数据库配置 3.environments的default属性与environment中的id属性连用确定当前使用哪一个数据库配置 --> <environments default="mysql"> <environment id="mysql"> <!-- 设置事务管理方式 type: 1.JDBC:手动管理事务 2.MANAGED:使用第三方事务管理插件 --> <transactionManager type="JDBC"></transactionManager> <!-- 设置连接池信息 type: 1.POOLED:使用默认打的连接池技术 2.UNPOOLED:不使用默认的连接池技术 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <!--XML文件中表示&需要用&--> <property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?useUnicode=true&characterEncoding=UTF-8&useSSL=false"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!--注册mapper.xml使用,如果不注册报错:Type interface 包.接口名 is not known to the MapperRegistry--> <mappers> <mapper resource="dao/UserDaoMapper.xml"/> </mappers> </configuration>
4.测试配置
import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import java.io.IOException; import java.io.InputStream; public class TestMybatis { /** * 获取Mybatis的连接 sqlSession * @throws IOException */ @Test public void getSqlSession() throws IOException { //1.读取Mybatis的主配置文件 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); //2.创建sqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过sqlSessionFactory获取sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //打印出sqlSession对象地址即表示配置成功(org.apache.ibatis.session.defaults.DefaultSqlSession@cc285f4) System.out.println(sqlSession); } }
5.建表以及相应实体类
- t_user表
use mybatis_test; CREATE TABLE t_user( id INT(11) PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20), sex VARCHAR(2), age INT(10), height DOUBLE, weihgt DOUBLE//注意和实体类中名称的不一致 );
- User实体类(此处需要导入lombok依赖,也可以手动填写getter,setter,构造,toStrong等方法)
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User { private Integer id; private String name; private String sex; private Integer age; private Double height; private Double weight;//注意和表中名称的不一致 }
6.DAO层以及对应的mapper实现
- 注意mapper文件按规范需要放入resources包下对应的目录下,否则会报错
package dao; import domains.User; import org.apache.ibatis.annotations.Param; import java.util.List; public interface UserDao { User selectById(Integer id); User selectByNameAndAge(@Param("name") String name, @Param("age") Integer age); List<User> selectAll(); void insert(User user); void deleteById(Integer id); void updateById(User user); // void updateById(@Param("user") User user); }
<?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:该xml对应的dao接口的全类名 --> <mapper namespace="dao.UserDao"> <!-- 增删改查分别对应不同的标签 id:对应的接口的方法名 resultType:确定查询接口的返回值的类型 --> <select id="selectById" resultType="domains.User"> select * from t_user where id = #{id} </select> <!-- 多个基本参数时,不能直接使用形参名 方法1:使用参数的index(select * from t_user where name = #{0} and age = #{1}),案例测试失败 方法2:使用param+index(select * from t_user where name = #{param1} and age = #{param2}) 方法3:接口使用@Param("别名")注解,然后在使用别名,推荐 --> <select id="selectByNameAndAge" resultType="domains.User"> select * from t_user where name = #{name} and age = #{age} </select> <!--如果返回结果是集合或者数组,resultType只需要使用泛型或类型即可,会自动分析--> <select id="selectAll" resultType="domains.User"> select * from t_user </select> <!--Mybatis默认不提交事务,做增删改时需要手动提交事务--> <!--没有返回值,resultType可以不写--> <insert id="insert"> insert t_user value(null,#{name},#{sex},#{age},#{height},#{weight}) </insert> <delete id="deleteById"> delete from t_user where id = #{abc} </delete> <!--注意:#{}中的数据需要和javaBean对象的属性保持一致,而左边的属性需要和mysql表中的字段名保持一致,例: weihgt = #{weight}--> <update id="updateById"> -- update t_user set name = #{user.name}, sex = #{user.sex}, age = #{user.age}, height = #{user.height}, weihgt = #{user.weight} where id = #{user.id} update t_user set name = #{name}, sex = #{sex}, age = #{age}, height = #{height}, weihgt = #{weight} where id = #{id} </update> </mapper>
7.测试mapper
@Test public void testSelect() throws IOException { //1.读取Mybatis的主配置文件 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); //2.创建sqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过sqlSessionFactory获取sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserDao mapper = sqlSession.getMapper(UserDao.class); //5.调用方法 User user = mapper.selectById(1); User useLi = mapper.selectByNameAndAge("李四", 18); List<User> users = mapper.selectAll(); //6.关闭连接 sqlSession.close(); System.out.println(user); System.out.println(useLi); System.out.println(users); } @Test public void testInsert() throws IOException { //1.读取Mybatis的主配置文件 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); //2.创建sqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过sqlSessionFactory获取sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserDao mapper = sqlSession.getMapper(UserDao.class); //5.调用方法 mapper.insert(new User(null,"李四","男",28,185.0,130.0)); //6.提交事务 sqlSession.commit(); //7.关闭连接 sqlSession.close(); } @Test public void testDelete() throws IOException { //1.读取Mybatis的主配置文件 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); //2.创建sqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过sqlSessionFactory获取sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserDao mapper = sqlSession.getMapper(UserDao.class); //5.调用方法 mapper.deleteById(1); //6.提交事务 sqlSession.commit(); //7.关闭连接 sqlSession.close(); } @Test public void testUpdate() throws IOException { //1.读取Mybatis的主配置文件 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); //2.创建sqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //3.通过sqlSessionFactory获取sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserDao mapper = sqlSession.getMapper(UserDao.class); //5.调用方法 mapper.updateById(new User(1,"李四","男",28,166.0,130.0)); //6.提交事务 sqlSession.commit(); //7.关闭连接 sqlSession.close(); }
8.编写业务层代码
package service; import domains.User; import java.util.List; public interface UserService { User selectById(Integer id); User selectByNameAndAge(String name, Integer age); List<User> selectAll(); void insert(User user); void deleteById(Integer id); void updateById(User user); }
package service.impl; import dao.UserDao; import domains.User; import org.apache.ibatis.session.SqlSession; import service.UserService; import utils.MybatisUtil; import java.util.List; public class UserServiceImpl implements UserService { public User selectById(Integer id) { SqlSession sqlSession = null; User user; try { //1.通过Mybatis工具类获取sqlSession sqlSession = MybatisUtil.getSqlSession(); //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserDao mapper = sqlSession.getMapper(UserDao.class); //3.调用方法 user = mapper.selectById(1); sqlSession.commit(); } catch (Exception e) { assert sqlSession != null; sqlSession.rollback(); throw new RuntimeException("userServiceImpl.selectById异常:"+e); } finally { //4.关闭sqlSession assert sqlSession != null; MybatisUtil.closeSqlSession(sqlSession); } System.out.println(user); return user; } public User selectByNameAndAge(String name, Integer age) { SqlSession sqlSession = null; User user; try { //1.通过Mybatis工具类获取sqlSession sqlSession = MybatisUtil.getSqlSession(); //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserDao mapper = sqlSession.getMapper(UserDao.class); //3.调用方法 user = mapper.selectByNameAndAge("李四",28); sqlSession.commit(); } catch (Exception e) { assert sqlSession != null; sqlSession.rollback(); throw new RuntimeException("userServiceImpl.sselectByNameAndAge异常:"+e); } finally { //4.关闭sqlSession assert sqlSession != null; MybatisUtil.closeSqlSession(sqlSession); } System.out.println(user); return user; } public List<User> selectAll() { SqlSession sqlSession = null; List<User> users; try { //1.通过Mybatis工具类获取sqlSession sqlSession = MybatisUtil.getSqlSession(); //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserDao mapper = sqlSession.getMapper(UserDao.class); //3.调用方法 users = mapper.selectAll(); sqlSession.commit(); } catch (Exception e) { assert sqlSession != null; sqlSession.rollback(); throw new RuntimeException("userServiceImpl.selectAll异常:"+e); } finally { //4.关闭sqlSession assert sqlSession != null; MybatisUtil.closeSqlSession(sqlSession); } System.out.println(users); return users; } public void insert(User user) { SqlSession sqlSession = null; try { //1.通过Mybatis工具类获取sqlSession sqlSession = MybatisUtil.getSqlSession(); //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserDao mapper = sqlSession.getMapper(UserDao.class); //3.调用方法 mapper.insert(new User()); sqlSession.commit(); } catch (Exception e) { assert sqlSession != null; sqlSession.rollback(); throw new RuntimeException("userServiceImpl.insert异常:"+e); } finally { //4.关闭sqlSession assert sqlSession != null; MybatisUtil.closeSqlSession(sqlSession); } } public void deleteById(Integer id) { SqlSession sqlSession = null; try { //1.通过Mybatis工具类获取sqlSession sqlSession = MybatisUtil.getSqlSession(); //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserDao mapper = sqlSession.getMapper(UserDao.class); //3.调用方法 mapper.deleteById(1); sqlSession.commit(); } catch (Exception e) { assert sqlSession != null; sqlSession.rollback(); throw new RuntimeException("userServiceImpl.deleteById异常:"+e); } finally { //4.关闭sqlSession assert sqlSession != null; MybatisUtil.closeSqlSession(sqlSession); } } public void updateById(User user) { SqlSession sqlSession = null; try { //1.通过Mybatis工具类获取sqlSession sqlSession = MybatisUtil.getSqlSession(); //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserDao mapper = sqlSession.getMapper(UserDao.class); //3.调用方法 mapper.updateById(new User()); sqlSession.commit(); } catch (Exception e) { assert sqlSession != null; sqlSession.rollback(); throw new RuntimeException("userServiceImpl.updateById异常:"+e); } finally { //4.关闭sqlSession assert sqlSession != null; MybatisUtil.closeSqlSession(sqlSession); } } }
7.方法参数绑定
1.一个基本类型参数(包含String)
- 方法只包含一个基本类型参数时,对应的mapper实现中#{}和${}理论上可以使用任意值都可以查询,但是规范与形参名保持一致,见名知意
void deleteById(Integer id);
<delete id="deleteById"> <!--不规范delete from t_user where id = #{abc}--> delete from t_user where id = #{id} <!--${}也可以实现,但是${}类似字符串拼接所以需要手动加上'' delete from t_user where id = '${id}'--> </delete>
2.多个基本类型参数(包含String)
方法包含多个基本类型参数时,推荐使用@Param注解
User selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);
<!--多个基本参数时,不能直接使用形参名(否则报错Parameter 'name' not found. Available parameters are [arg1, arg0, param1, param2]) 方法1:使用参数的index(select * from t_user where name = #{arg0} and age = #{arg1}),案例测试成功 方法2:使用param+index(select * from t_user where name = #{param1} and age = #{param2}),案例测试成功 方法3:接口使用@Param("别名")注解,然后在使用别名,推荐 --> <select id="selectByNameAndAge" resultType="domains.User"> select * from t_user where name = #{name} and age = #{age} </select>
上述#{}都可以使用${}代替,但是需要手动加上’’
方法一和方法二的原理:Mybatis会将这些参数放在一个map集合中,并以两种方式存储
- 1.以arg0,arg1,…为键,以参数为值
- 2.以param1,parm2,…为键,以参数为值
因此只需要通过#{}和${}以键的方式访问即可
补充:方法4(通过传入一个map集合,手动将值存入进入map集合)
List<User> selectByMapNameAndAge(Map<String, Object> map);
<!--List<User> selectByMapNameAndAge(Map<String, Object> map);--> <select id="selectByMapNameAndAge" resultType="user"> select * from t_user where name = #{name} and age = #{age} </select>
@Test public void testMybatisUtil(){ //1.通过工具类获取SqlSession对象 SqlSession sqlSession = MybatisUtil.getSqlSession(); //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //3.调用方法 HashMap<String, Object> map = new HashMap<String, Object>(); map.put("name","李四"); map.put("age",18); List<User> users = mapper.selectByMapNameAndAge(map); //4.关闭连接,注意如果是增删改方法需要手动提交事务后再关闭连接 MybatisUtil.closeSqlSession(sqlSession); System.out.println(users); }
3.对象类型参数
一个对象类型
- 一个对象类型时可以直接在#{}中使用对象的属性名
多个对象类型
- 多个对象类型时可以使用@Param注解然后在#{}中使用别名.属性名用以区分多个对象
void updateById(User user); // void updateById(@Param("user") User user);
<update id="updateById"> -- update t_user set name = #{user.name}, sex = #{user.sex}, age = #{user.age}, height = #{user.height}, weihgt = #{user.weight} where id = #{user.id} update t_user set name = #{name}, sex = #{sex}, age = #{age}, height = #{height}, weihgt = #{weight} where id = #{id} </update>
8.MybatisUtil工具类简化配置
package utils; 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 MybatisUtil { private static SqlSessionFactory sqlSessionFactory; //程序运行后只需要加载一次配置文件和SqlSessionFactory static { //1.加载Mybatis的主配置文件 InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream("mybatis-config.xml"); } catch (IOException e) { throw new RuntimeException("MybatisUtil配置文件加载异常" + e); } //2.使用sqlSessionFactoryBuilder创建sqlSessionFactory对象 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } //获取sqlSession public static SqlSession getSqlSession(){ ///3.通过sqlSessionFactory获取sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); return sqlSession; } //关闭sqlSession public static void closeSqlSession(SqlSession sqlSession){ sqlSession.close(); } }
@Test public void testMybatisUtil(){ //1.通过工具类获取SqlSession对象 SqlSession sqlSession = MybatisUtil.getSqlSession(); //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserDao mapper = sqlSession.getMapper(UserDao.class); //3.调用方法 List<User> users = mapper.selectAll(); //4.关闭连接,注意如果是增删改方法需要手动提交事务后再关闭连接 MybatisUtil.closeSqlSession(sqlSession); System.out.println(users); }
9.service业务层代码
package service; import domains.User; import java.util.List; public interface UserService { List<User> selectAll(); }
package service.impl; import dao.UserDao; import domains.User; import org.apache.ibatis.session.SqlSession; import service.UserService; import utils.MybatisUtil; import java.util.List; public class UserServiceImpl implements UserService { public List<User> selectAll() { SqlSession sqlSession = null; List<User> users; try { //1.通过Mybatis工具类获取sqlSession sqlSession = MybatisUtil.getSqlSession(); //2.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserDao mapper = sqlSession.getMapper(UserDao.class); //3.调用方法 users = mapper.selectAll(); sqlSession.commit(); } catch (Exception e) { assert sqlSession != null; sqlSession.rollback(); throw new RuntimeException("userServiceImpl.selectAll异常:"+e); } finally { //4.关闭sqlSession assert sqlSession != null; MybatisUtil.closeSqlSession(sqlSession); } System.out.println(users); return users; } }
10.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> <!-- 标签的顺序需要按如下设置 1.properties 属性 2.settings 设置 通过该标签改变Mybatis的内部配置 3.typeAliases 类型别名 4.typeHandlers 类型处理器 5.objectFactory 对象工厂 6.objectWrapperFactory 7.reflectorFactory 8.plugins 插件 9.environments 环境变量 1.transactionManager 事务管理器 2.dataSource 数据源 10.databaseIdProvider 数据库厂商标识 11.mappers 映射器 --> <!--properties标签提供了引入外部文件或者设置变量的方法 1.引入外部文件: resource:外部文件相对路径 url:外部文件绝对路径,注意绝对路径前要加上file: 2.设置变量:可以使用${}引入该变量 <properties> <property name="testPojo" value="domains"/> </properties> <package name="${testPojo}"/> --> <properties resource="db.properties"/> <!-- <properties url="file:E:\MySelfProject\Mybatis\src\main\resources\db.properties"/>--> <settings> <!--配置缓存的全局开关true|false 默认为true--> <setting name="cacheEnabled" value="true"/> <!--延迟加载的全局开关true|false 默认为false 开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态 --> <setting name="lazyLoadingEnabled" value="true"/> <!--是否允许单一语句返回多结果集(需要兼容驱动true|false 默认为true--> <setting name="multipleResultSetsEnabled" value="true"/> <!--使用列标签代替列名,默认为true--> <setting name="useColumnLabel" value="true"/> <!--允许JDBC 支持自动生成主键,需要驱动兼容。如果设置为 true,则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作,默认为false--> <setting name="useGeneratedKeys" value="false"/> <!--指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射。 PARTIAL 表示只会自动映射,没有定义嵌套结果集和映射结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套) NONE、PARTIAL、FULL PARTIAL--> <setting name="autoMappingBehavior" value="PARTIAL"/> <!--指定自动映射当中未知列(或未知属性类型)时的行为,默认是不处理,只有当日志级别达到 WARN 级别或者以下,才会显示相关日志,如果处理失败会抛出 SqlSessionException 异常 NONE、WARNING、FAILING NONE--> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <!--配置默认的执行器。SIMPLE 是普通的执行器;REUSE 会重用预处理语句(prepared statements);BATCH 执行器将重用语句并执行批量更新 SIMPLE、REUSE、BATCH SIMPLE--> <setting name="defaultExecutorType" value="SIMPLE"/> <!--设置超时时间,它决定驱动等待数据库响应的秒数 任何正整数 Not Set (null)--> > <setting name="defaultStatementTimeout" value="25"/> <!--设置数据库驱动程序默认返回的条数限制,此参数可以重新设置 任何正整数 Not Set (null)--> <setting name="defaultFetchSize" value="100"/> <!--允许在嵌套语句中使用分页(RowBounds)。如果允许,设置 false,默认值false--> <setting name="safeRowBoundsEnabled" value="false"/> <!--是否开启自动驼峰命名规则映射--> <setting name="mapUnderscoreToCamelCase" value="false"/> <!--利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速联复嵌套査询。默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlScssion 的不同调用将不会共享数据--> <setting name="localCacheScope" value="SESSION"/> <!--当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型--> <setting name="jdbcTypeForNull" value="OTHER"/> <!--指定哪个对象的方法触发一次延迟加载 — equals、clone、hashCode、toString--> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings> <!-- 注意 1.无论哪种形式起别名,别名不区分大小写 2.mapper文件中的namespace不能使用别名,只能使用全类名 3.可以使用@Alias("别名")注解直接在实体类上取别名 --> <typeAliases> <!-- typeAlias标签配置实体类别名,然后在mapper文件中可以在resultType标签中直接使用别名 1.type:实体类全类名 2.alias:实体类别名 缺点:每一个实体类都需要一个typeAlias标签,推荐使用package --> <!-- <typeAlias type="domains.User" alias="user"/>--> <!-- package标签给指定包下的所有类批量起别名,默认小驼峰(User->user) 1.name:需要别名实体类所在的包 --> <package name="domains"/> </typeAliases> <!-- environments标签配置数据库环境 1.environments标签下可以配置多个environment标签 2.每一个environment标签相当于一个数据库配置 3.environments的default属性与environment中的id属性连用确定当前使用哪一个数据库配置 --> <environments default="mysql"> <environment id="mysql"> <!-- 设置事务管理方式 type: 1.JDBC:JDBC原生事务管理,即手动管理事务 2.MANAGED:使用第三方事务管理插件 --> <transactionManager type="JDBC"></transactionManager> <!-- 设置连接池信息 type: 1.POOLED:使用默认的连接池技术 2.UNPOOLED:不使用默认的连接池技术 --> <dataSource type="POOLED"> <!-- <property name="driver" value="com.mysql.jdbc.Driver"/>--> <!-- <!–XML文件中表示&需要用&–>--> <!-- <property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?useUnicode=true&characterEncoding=UTF-8&useSSL=false"/>--> <!-- <property name="username" value="root"/>--> <!-- <property name="password" value="root"/>--> <!--使用properties引入配置文件后使用${}写入键的方式写入数据--> <property name="driver" value="${driver}"/> <!--XML文件中表示&需要用&--> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <!--每一个mapper.xml都需要在mybatis的核心配置文件注册--> <!--注册mapper.xml使用,如果不注册会报错Type interface dao.UserDao is not known to the MapperRegistry.--> <mappers> <!--注册一个mapper.xml文件--> <!-- <mapper resource="dao/UserDaoMapper.xml"/>--> <!--批量注册,自动扫描包下的mapper文件并注册, 注意使用包扫描mapper时mapper文件名需要dao接口名一致,否则会报错BindingException: Invalid bound statement (not found): dao.UserDao.selectById 规范接口和mapper文件都统一用xxxMapper命名 且mapper文件需要和dao接口所在包层一致,resources下包名之间用/分割不能用. --> <package name="dao"/> </mappers> </configuration>
- idea设置mybatis-config.xml文件模板
11.xxxMapper.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:该xml对应的dao接口的全类名 --> <mapper namespace="dao.UserMapper"> <!--开启二级缓存 type:表示自定义的缓存的类,没有的情况下会存在一个默认的缓存类PerpetualCache.class。 blocking:是否采用阻塞的方式(加锁) eviction:缓存到达上限时的淘汰策略 LRU – 会淘汰使用频次最低的缓存内容 FIFO – 先进先出:按对象进入缓存的顺序来移除它们。 SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。 WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象 flushInterval:定义缓存定时清空策略(ms),默认情况是不设置,没有刷新间隔,缓存仅仅调用语句时刷新 readOnly:属性可以被设置为 true 或 false,默认是 false size:缓存的最大容量,默认值是 1024 --> <cache size="1024" eviction="LRU" flushInterval="60000"/> <!-- <cache type="" blocking="" eviction="" flushInterval="" readOnly="" size=""/>--> <!--1.可重复使用sql片段,利用include标签和对应的refid绑定该sql片段,可以提高效率 <select id="selectById" resultType="user"> <include refid="t_user_id"></include> from t_user where id = #{id} </select> --> <sql id="t_user_id"> select id,name,sex,age,height,weihgt </sql> <!--2.手动映射属性和字段,可以只映射不对应的字段 id:引用标识 type:实体类的全类名或别名 --> <resultMap id="userMap" type="user"> <!-- id映射主键 property: 实体类的属性名 column: 表的字段名 --> <id property="id" column="id"/> <!-- result映射除主键外的普通字段--> <result property="weight" column="weihgt"/> <!-- 当一个属性为对象时使用association标签 property: 对象属性的属性名 javaType: 对象属性的类型 --> <association property="card" javaType="domains.Card"> <id property="userId" column="user_id"/> <result property="userCard" column="user_card"/> </association> <!-- 集合属性映射使用collection标签 property:对象属性名 javaType:集合的类型 ofType:泛型的类型 --> <collection property="orders" javaType="java.util.List" ofType="domains.Order"> <id property="orderId" column="order_id"/> <result property="orderCode" column="order_code"/> </collection> </resultMap> <!-- <cache type="" blocking="" eviction="" flushInterval="" readOnly="" size=""/>--> <!-- <cache-ref namespace="" />--> <!-- <parameterMap id="" type="" >--> <!-- <parameter property=""></parameter>--> <!-- </parameterMap>--> <!-- 增删改查分别对应不同的标签 id:对应的接口的方法名 resultType:确定查询接口的返回值的类型 --> <!-- <select id="selectById" resultType="user">--> <!-- select * from t_user where id = #{id}--> <!-- </select>--> <!--使用resultMap手动映射,resultMap和resultType只能使用一个--> <select id="selectById" resultMap="userMap"> select * from t_user where id = ${id}<!--或#{id}--> </select> <!-- 多个基本参数时,不能直接使用形参名 方法1:使用参数的index(select * from t_user where name = #{arg0} and age = #{arg1}) 方法2:使用param+index(select * from t_user where name = #{param1} and age = #{param2}) 方法3:接口使用@Param("别名")注解,然后在使用别名,推荐 --> <select id="selectByNameAndAge" resultType="user"> select * from t_user where name = #{name} and age = #{age} </select> <!--如果返回结果是集合或者数组,resultType只需要使用泛型或类型即可,会自动分析--> <select id="selectAll" resultType="user"> select * from t_user </select> <!--Mybatis默认不提交事务,做增删改时需要手动提交事务--> <!--没有返回值,resultType可以不写--> <insert id="insert"> insert t_user value(null,#{name},#{sex},#{age},#{height},#{weight}) </insert> <delete id="deleteById"> delete from t_user where id = #{abc} </delete> <update id="updateById"> -- update t_user set name = #{user.name}, sex = #{user.sex}, age = #{user.age}, height = #{user.height}, weihgt = #{user.weight} where id = #{user.id} update t_user set name = #{name}, sex = #{sex}, age = #{age}, height = #{height}, weihgt = #{weight} where id = #{id} </update> <!-- 1.在xml文件中 > 使用 > 表示 》 使用 >= 表示 < 使用 < 表示 《 使用 <= 表示 2.#{}和${}的区别 1.#{} select * from t_user id = ? #{}类似于占位符赋值 1.可以防止sql注入 2.只能拼接数据,不能拼接关键字(字段名,运算符等) 2.${} select * from t_user id = 1 ${}类似于字符串拼接 1.容易被sql注入 2.可以绑定关键字 预编译时sql语句会提前发送给Mysql数据库,然后进行语法检测,语义检测,拆分sql 语法检测:查看sql语法是否正确 语义检测:查看sql中的关键字是否存在 拆分sql:根据关键词将sql语句进行拆分 ${}中的关键字会被拆分,容易被注入,例:id=xxx or 1=1 #{}中的关键字不会被识别,只会被整体当成一个值 原理:跳过语义检测阶段,防止值中关键字被识别 --> </mapper>
- idea设置mapper文件模板
1.三种解决字段名和属性名映射关系的方式
- 1.通过起别名的方式,将表中的下划线字段名起别名为驼峰形式
<select id="selectUserByTableName" resultType="user"> select id,name,sex,age,height,weihgt weight from ${tableName} </select>
- 2.通过全局配置mapUnderscoreToCamelCase属性
<!--是否开启自动驼峰命名规则映射,默认false不开启 自动将_转换为驼峰,例:emp_name -> empName --> <setting name="mapUnderscoreToCamelCase" value="true"/>
- 3.通过resultMap手动解决映射关系
12.表连接查询
1.一对一连接查询
A表中有且只有一条数据与B表相关
1.可以通过级联属性赋值查询解决
<result property="card.userId" column="user_id"/>
2.也可以通过association 标签解决
例:一个用户(t_user)只有一个识别卡(t_card)
表
CREATE TABLE t_user( id INT(11) PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20), sex VARCHAR(2), age INT(10), height DOUBLE, weihgt DOUBLE ); ------------------------------------- CREATE TABLE t_card( card_id INT(11) PRIMARY KEY AUTO_INCREMENT, user_card VARCHAR(18) not null, user_id INT(11) REFERENCES t_user(id) ); ------------------------------------- SELECT * FROM t_user LEFT JOIN t_card on t_user.id = t_card.user_id
实体类
package domains; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User { private Integer id; private String name; private String sex; private Integer age; private Double height; private Double weight; private Card card;//根据需求定谁包含谁的属性 } ------------------------------------- package domains; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class Card { private Integer cardId; private String userCard; private Integer userId; }
package dao; import domains.User; import org.apache.ibatis.annotations.Param; import java.util.List; public interface UserMapper { List<User> selectAll(); }
<?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="dao.UserMapper"> <resultMap id="userMap" type="user"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="sex" column="sex"/> <result property="age" column="age"/> <result property="height" column="height"/> <result property="weight" column="weihgt"/> <result property="weight" column="weihgt"/> <association property="card" javaType="domains.Card"> <id property="userId" column="user_id"/> <result property="userCard" column="user_card"/> </association> </resultMap> <select id="selectAll" resultMap="userMap"> SELECT * FROM t_user LEFT JOIN t_card on t_user.id = t_card.user_id </select> </mapper>
@Test public void testMybatisUtil(){ //1.通过工具类获取SqlSession对象 SqlSession sqlSession = MybatisUtil.getSqlSession(); //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //3.调用方法 List<User> users = mapper.selectAll(); //4.关闭连接,注意如果是增删改方法需要手动提交事务后再关闭连接 MybatisUtil.closeSqlSession(sqlSession); System.out.println(users); }
2.一对多连接查询
- A表中的一条数据B表有多条数据对应
- 例:一个用户(t_user)有多个订单(t_order)
- 表
CREATE TABLE t_order( order_id INT(10) PRIMARY KEY auto_increment, order_code INT(20), user_id int(11) REFERENCES t_user(id) ); ------------------------------------------- SELECT * FROM t_user LEFT JOIN t_order on t_user.id = t_order.user_id
- 实体类
package domains; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @Data @NoArgsConstructor @AllArgsConstructor public class User { private Integer id; private String name; private String sex; private Integer age; private Double height; private Double weight; private List<Order> orders; } ---------------------------------------- package domains; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class Order { private Integer orderId; private String orderCode; //作为外键的字段一般不在实体类中体现 // private Integer userId; }
<?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="dao.UserMapper"> <resultMap id="userMap" type="user"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="sex" column="sex"/> <result property="age" column="age"/> <result property="height" column="height"/> <result property="weight" column="weihgt"/> <result property="weight" column="weihgt"/> <collection property="orders" javaType="java.util.List" ofType="domains.Order"> <id property="orderId" column="order_id"/> <result property="orderCode" column="order_code"/> </collection> </resultMap> <select id="selectAll" resultMap="userMap"> SELECT * FROM t_user LEFT JOIN t_order on t_user.id = t_order.user_id </select> </mapper>
3.对多对连接查询
- A表中的一条数据关联B表中的多条数据
- B表中的一条数据关联A表中的多条数据
- 例:一个用户可以选择多门课程,一个课程也拥有多个用户
- 表
CREATE TABLE t_course( course_id INT(10) PRIMARY KEY AUTO_INCREMENT, course_name VARCHAR(20), course_time INT(10) ); -- 多对多关系不能使用外键解决 -- 需要添加第三张表(关系表) -- 关系表只有两个字段 -- A表的主键,B表的逐渐 CREATE TABLE t_user_course( user_id INT(11) REFERENCES t_user(id), course_id INT(10) REFERENCES t_course(course_id) ); --------------------------- select a.id,a.name,c.course_name,c.course_time from t_user a left join t_user_course b on a.id = b.user_id left join t_course c on b.course_id = c.course_id;
- 实体类
- 根据具体需求编写实体类,将其变成一对多
- 例:查询所有用户以及用户们选修的科目
package domains; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @Data @NoArgsConstructor @AllArgsConstructor public class User { private Integer id; private String name; private String sex; private Integer age; private Double height; private Double weight; private List<Course> courses; } --------------------- package domains; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Course { private Integer courseId; private String courseName; private Integer courseTime; }
<?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="dao.UserMapper"> <resultMap id="userMap" type="user"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="sex" column="sex"/> <result property="age" column="age"/> <result property="height" column="height"/> <result property="weight" column="weihgt"/> <result property="weight" column="weihgt"/> <collection property="courses" javaType="java.util.List" ofType="domains.Course"> <id property="courseId" column="course_id"/> <result property="courseName" column="course_name"/> <result property="courseTime" column="course_time"/> </collection> </resultMap> <select id="selectAll" resultMap="userMap"> select a.id,a.name,c.course_name,c.course_time from t_user a left join t_user_course b on a.id = b.user_id left join t_course c on b.course_id = c.course_id; </select> </mapper>
13特殊查询和添加
1.模糊查询
- 关键字:like
- MySQL:
select * from t_user where name like '%王%'
- Mybatis:
select * from t_user where name like concat('%',#{name},'%')
或select * from t_user where name like '%${name}%'
或select * from t_user where name like "%"#{name}"%"
2.区间查询
- 关键字:>= xxx <= xxx / between xxx and xxx(全闭区间)
- MySQL:
select * from t_user where age >= 20 and age<= 40
或select * from t_user where age BETWEEN 20 and 40
- Mybatis:
select * from t_user where age >= #{minAge} and age <= #{maxAge}
或select * from t_user where age BETWEEN #{minAge} and #{maxAge}
3.添加时获得主键值
- 前提:主键自增长
- 方法一:使用selectKey子标签
<insert id="insert"> <!-- keyProperty:查询语句的值赋值给对象的一个属性 order: BEFORE:在添加语句执行前执行查询 AFTER:在添加语句执行后执行查询 resultType:返回值类型 --> <selectKey keyProperty="id" order="AFTER" resultType="int"> <!--LAST_INSERT_ID()方法可以获取最后一次增加的自增长的值--> select LAST_INSERT_ID() </selectKey> insert t_user value(null,#{name},#{sex},#{age},#{height},#{weight}) </insert>
- 方法二:使用useGeneratedKeysv属性
<!--useGeneratedKeys设置为 true 时,表示如果插入的表id以自增列为主键,则允许 > JDBC 支持自动生成主键,并可将自动生成的主键id返回 keyProperty:实体类中对应的主键 keyColumn:表中的自增主键 --> <insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="id"> insert t_user value(null,#{name},#{sex},#{age},#{height},#{weight}) </insert>
4.批量删除
- 1.当参数类型为String类型使用${别名}可以批量删除,不能使用#{}因为它会自动拼接’'(Data truncation: Truncated incorrect DOUBLE value: ‘1,2,3’)
void deleteByIds(@Param("ids") String ids);
<delete id="deleteByIds"> delete from t_user where id in (${ids}) </delete>
- 2.当参数类型为数组或集合时可以使用动态标签中的forEach标签
5.批量添加
void insertBatch(@Param("users") List<User> users);
<!--void insertBatch(List<User> users);--> <insert id="insertBatch" useGeneratedKeys="true" keyProperty="id" keyColumn="id"> insert t_user values <foreach collection="users" item="user" separator=","> (null,#{user.name},#{user.sex},#{user.age},#{user.height},#{user.weight}) </foreach> </insert>
6.动态设置表名
- 如果一张表中的数据过多,不利于性能,所以会将该表拆分成多张表共同存储一张表中的数据
- 此时查询同一张表中的数据时表名可能不相同,所以SQL语句中的表名需要动态设置
- 动态设置表名需要使用${},使用#{}会报错
(You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''t_user'' at line 1)
List<User> selectUserByTableName(@Param("tableName") String tableName);
<select id="selectUserByTableName" resultType="user"> select * from ${tableName} </select>
14.动态SQL
- Mybatis中动态sql通过标签实现
1.if标签
- 条件判断,如果test中的条件满足才会执行if标签中的语句
- 可以通过在where后面加上恒成立表达式(1=1)防止多余的and关键字
<select id="selectByNameLikeAndAgeRange" resultType="user"> select * from t_user <!--test标签中的连接符为and或or--> <if test="name != null and name !=''"> <!--如果#{name}的值为空会报错,所以需要使用if标签进行条件判断,test满足才会被执行--> name like concat('%',#{name},'%') </if> <!--如果值为null会报错,所以也需要if标签进行条件判断--> <if test="minAge != null and minAge > 0"> and age >= #{minAge} </if> <if test="maxAge != null and maxAge > minAge"> and age <= #{maxAge} </if> </select>
2.where标签
- 代替where关键字(如果where标签中有内容时会自动生成where关键字,如果没有内容则不会生成where关键字)
- 使用if标签时会出现多余的关键字,where标签可以自动去除if标签中头部多余的and/or关键字(只能去除头部多余的关键字)
<select id="selectByNameLikeAndAgeRange" resultType="user"> select * from t_user <where> <!--test标签中的连接符为and或or--> <if test="name != null and name !=''"> <!--如果#{name}的值为空会报错,所以需要使用if标签进行条件判断,test满足才会被执行--> and name like concat('%',#{name},'%') </if> <!--如果值为null会报错,所以也需要if标签进行条件判断--> <if test="minAge != null and minAge > 0"> and age >= #{minAge} </if> <if test="maxAge != null and maxAge > minAge"> and age <= #{maxAge} </if> </where> </select>
3.set标签
- 代替修改语句中的set关键字(一般只使用在修改语句中)
- 自动清除修改语句中多余的逗号(只能自动消除if标签结尾的逗号)
<update id="updateById"> update t_user <set> <if test="name != null"> name = #{name}, </if> <if test="sex != null"> sex = #{sex}, </if> <if test="age != null"> age = #{age}, </if> <if test="height != null"> height = #{height}, </if> <if test="weight != null"> weihgt = #{weight} </if> </set> where id = #{id} </update>
4.forEach标签
- 一般用在in中
- 注意批量删除时,集合或数组在循环取值时,必须使用@Param注解为参数名其别名,否则会报错
void deleteByIds(@Param("ids") List<Integer> ids);
<delete id="deleteByIds"> delete from t_user where id in <!-- 循环遍历标签 collection:循环的参数 item:每次循环的的元素名 open:开始的标志 separator:每个元素的间隔符 close:结束的标志 index:表示迭代中每次迭代到的下标位置 --> <foreach collection="ids" item="id" open="(" separator="," close=")"> #{id} </foreach> <!--delete from t_user where <foreach collection="ids" item="id" separator="or"> id = #{id} </foreach>--> </delete>
5.choose、when、otherwise标签
- 类似Java 中的 switch 语句
- 注意:when标签中不需要加and/or关键字,因为只会执行一个分支
<select id="selectUser" resultType="domains.User"> select * from user <where> <choose> <when test="name!= null and name!= ''"> age =#{age} </when> <when test="age!= null and age!= ''"> name =#{name} </when> <otherwise> status = 'true' </otherwise> </choose> </where> </select>
6.trim标签
- 特点:更加灵活添加或去除前缀或后缀,若标签中没有内容则trim没有任何效果
- prefix:在trim标签内sql语句加上前缀
- suffix:在trim标签内sql语句加上后缀
- prefixOverrides:指定去除多余的前缀内容,如:prefixOverrides=“AND|OR”,去除trim标签内sql语句多余的前缀"AND"或者"OR"
- suffixOverrides:指定去除多余的后缀内容,suffixOverrides=“,”,去除trim标签内sql语句多余的后缀","
<update id="updateById"> update t_user <trim prefix="set" suffixOverrides=","> <if test="name != null"> name = #{name}, </if> <if test="sex != null"> sex = #{sex}, </if> <if test="age != null"> age = #{age}, </if> <if test="height != null"> height = #{height}, </if> <if test="weight != null"> weihgt = #{weight} </if> </trim> where id = #{id} </update>
15.Mybatis缓存
- 当用户第一次做查询时,Mybatis不仅将结果返回给用户,还将结果保存在缓存中
- 如果用户第二次做相同的查询时,Mybatis不再从数据库中进行查询,而是从缓存中直接提取数据,节省了与数据库创建的IO流的开销
- 注意:必须是第二次查询,且与之前的查询相同时缓存才有效
- 学习步骤
- 1.如何开启缓存
- 2.缓存作用范围
- 3.脏数据的处理
- 脏数据:缓存中的数据由于增删改的操作和数据库中真实的数据不一致
1.一级缓存
- 作用范围小,限制大,实践中不使用
1.如何开启缓存
- 默认开启
2.缓存作用范围
- 同一个sqlSession创建的mapper可以共用缓存
- 注意:不能提交事务,一旦提交事务,缓存消失,就会提交多个语句
3.一级缓存失效的4种情况
- 1.不同的sqlSeesion对应不同的一级缓存
- 2.同一个sqlSession但查询条件不同
- 3.同一个sqlSession两次查询查询期间执行了任何一次增删改操作
- 4.同一个sqlSession两次查询查询期间手动清空了缓存(sqlSession.clearCache())
3.脏数据的处理
- 一旦执行增删改就立即清空缓存
2.二级缓存
- 作用范围大,限制小,实践中使用
1.如何开启缓存
- 1.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> <settings> <setting name="cacheEnabled" value="true"/> </settings> </configuration>
- 2.对应的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="dao.UserMapper"> <cache size="1024" eviction="LRU" flushInterval="60000"/> <!-- <cache type="" blocking="" eviction="" flushInterval="" readOnly="" size=""/>--> </mapper>
- 3.sqlSession关闭或提交之后有效
sqlSession.close()/sqlSession.commit()
- 4.对应的实体类实现可序列化接口
package domains; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Getter; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.List; @Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private static final long serialVersionUID = -5704008945291005272L; private Integer id; private String name; private String sex; private Integer age; private Double height; private Double weight; private List<Course> courses; }
2.缓存作用范围
- 同一个sqlSessionFactory创建的所有sqlSession共用缓存
- 注意:二级缓存必须提交事务,这样数据才能被保存到缓存中
- 二级缓存必须在sqlSession关闭或提交后之后有效,如果关闭或提交sqlSession则数据存放在二级缓存中,否则存放在一级缓存中
3.二级缓存失效的1种情况
- 3.同一个sqlSessionFactory两次查询查询期间执行了任何一次增删改操作
4.脏数据的处理
- 默认:一旦执行增删改,就清空缓存
- 自定义:insert,delete,update,select标签中都有flushCache属性,如果为true则表示一执行该sql操作就清空缓存,false则表示不清空
- insert,delete,update默认为true,select默认为false
- useCache 是否使用缓存。可以在每个标签上加上该属性细化缓存
- 从缓存中读取,如果存在,那么直接返回。不存在,则查询数据库并更新缓存。
- flushCache 是否更新缓存
- 强制更新缓存
3.Mybatis缓存查询的顺序
- 1.先查二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以直接使用
- 2.如果二级缓存没有命中,再查询一级缓存
- 3.如果一级缓存也没有命中,则查询数据库
- 4.sqlSession关闭之前,sqlSession中的数据默认保存在一级缓存,关闭之后,一级缓存中的数据会写入二级缓存
4.Mybatis整合第三方缓存EHCache
1.添加jar包
<!--Mybatis EHCache整合包--> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency>
- mybatis-ehcache:Mybatis和EHCache的整合包
- ehcache:EHCache核心包
2.添加配置文件ehcache.xml
<?xml version="1.0" encoding="UTF-8" ?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!--磁盘保存路径--> <diskStore path="E:\ehcache"/> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>
3.设置二级缓存的类型
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
16.Mybatis整合slf4j
- 存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志
1.添加jar包
<!--Mybatis EHCache整合包--> <dependency> <!--slf4j日志门面的一个具体实现--> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
- slf4j-api:SLF4J日志门面包
- logback-classic:支持SLF4J门面接口的一个具体实现
2.添加配置文件logback.xml
<?xml version="1.0" encoding="UTF-8" ?> <configuration debug="true"> <!--指定日志输出的位置--> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!--日志输出的格式--> <!--按照顺序分别是:时间,日志级别,线程名称,打印日志的类,日志主题内容,换行--> <pattern>[%d{HH:mm:ss.SSS}] [%-5Level] [%thread] [%logger] [%msg]%n</pattern> </encoder> </appender> <!--设置全局日志级别。日志级别按顺序分别是:DEBUG,INFO,WARN,ERROR--> <!--指定任何一个日志级别都只打印当前级别和后面级别的日志--> <root level="DEBUG"> <!--指定打印日志的appender,通过STDOUT引用上文的appender--> <appender-ref ref="STDOUT"/> </root> <!--根据特殊需求指定局部日志级别--> <logger name="com.wd.mapper" level="DEBUG"/> </configuration>
17.Mybatis逆向工程
- 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate,Ebean支持正向工程
- 逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
- 1.java实体类
- 2.Mapper接口
- 3.Mapper映射文件
1.创建逆向工程的步骤
1.创建Maven项目
2.添加依赖和插件
<?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>org.example</groupId> <artifactId>Mybatis_MBG</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <!--Mybatis核心包--> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <!--控制Maven在构建过程中相关配置--> <build> <!--构建过程中用到的插件--> <plugins> <!--具体插件,逆向工程的操作是以构建过程中插件形式出现的--> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.0</version> <!--插件的依赖--> <dependencies> <!--逆向工程的核心包--> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.2</version> </dependency> <!--MySQL驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> </dependencies> </plugin> </plugins> </build> </project>
3.创建Mybatis的核心配置文件
- 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> <properties resource="db.properties"/> <typeAliases> <package name="domains"/> </typeAliases> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <package name="mapper"/> </mappers> </configuration>
- log4j.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"> <param name="Encoding" value="UTF-8"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m(%F:%L) \n"/> </layout> </appender> <logger name="java.sql"> <level value="debug"/> </logger> <logger name="org.apache.ibatis"> <level value="info"/> </logger> <root> <level value="debug"/> <appender-ref ref="STDOUT"/> </root> </log4j:configuration>
- db.properties
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username=root password=root
4.创建逆向工程的配置文件
- 文件名必须是generatorConfig.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- targetRuntime:执行生成逆向工程的版本 Mybatis3Simple:(简化版)生成基本的CRUD Mybatis3:(完整版)生成带条件地CRUD context的内容必须匹配 property* plugin* commentGenerator (connectionFactory|jdbcConnection) javaTypeResolver javaModelGenerator sqlMapGenerator javaClientGenerator table+ --> <context id="DB2Tables" targetRuntime="MyBatis3Simple"> <!--数据库的连接信息--> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis_test" userId="root" password="root" > </jdbcConnection> <!--JavaBean的生成策略 targetPackage:指定生成JavaBean的包目录 targetProject:指定文件下 --> <javaModelGenerator targetPackage="com.domains" targetProject=".\src\main\java"> <!--enableSubPackages:是否可以使用子包,true表示com.domains是多层目录,false表示com.domains只是目录名--> <property name="enableSubPackages" value="true" /> <!--trimStrings:去掉字符串前后的空格,逆向将表中的字段名转换成实体类的属性名,去掉字段名中的前后空格--> <property name="trimStrings" value="true" /> </javaModelGenerator> <!--SQL映射文件的生成策略--> <sqlMapGenerator targetPackage="com.mapper" targetProject=".\src\main\resources"> <property name="enableSubPackage" value="true" /> </sqlMapGenerator> <!--Mapper接口的生成策略--> <javaClientGenerator type="XMLMAPPER" targetPackage="com.mapper" targetProject=".\src\main\java"> <property name="enableSubPackage" value="true" /> </javaClientGenerator> <!--逆向分析的表 tableName:设置为*号可以对应所有的表,此时不需要指定domainObjectName,默认大驼峰 domainObjectName:属性指定生成出来的实体类的类名 Mapper接口和Mapper映射文件的名称会根据实体类的名称自动生成:实体类名+Mapper --> <table tableName="t_user" domainObjectName="User"/> <table tableName="t_order" domainObjectName="Order"/> <table tableName="t_card" domainObjectName="Card"/> <table tableName="t_course" domainObjectName="Course"/> </context> </generatorConfiguration>
5.构建
- 点击即可
- 会报异常(未解决)
org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: java.lang.NullPointerException ### The error may exist in com/wd/mapper/UserMapper.xml ### The error may involve com.wd.mapper.UserMapper.selectAll ### The error occurred while executing a query ### Cause: java.lang.NullPointerException
2.逆向工程完整版
- 修改配置文件的版本即可
<context id="DB2Tables" targetRuntime="Mybatis3">
18.Mybatis分页插件
- 分页是在查询语句后面使用limit关键字
- limit index,pageSize
- index:当前页的起始索引
- pageSize:每页显示的条数
- pageNum:当前页的页码
- index = (pageNum-1) * pageSize
1.Mybatis分页插件使用步骤
1.添加依赖
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.2.0</version> </dependency>
2.配置分页插件
- mybatis-config.xml文件中添加分页插件
<plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins>
3.使用分页插件
@Test public void testPageHelper(){ //1.通过工具类获取SqlSession对象 SqlSession sqlSession = MybatisUtil.getSqlSession(); //4.通过sqlSession获取Mybatis为dao接口创建的代理实现类 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //开启分页 PageHelper.startPage(1,2); 3.调用方法 List<User> users = mapper.selectAll(); //将分页信息封装到PageInfo对象中(查询结果,导航页条数) PageInfo<User> userPageInfo = new PageInfo<User>(users, 5); //4.关闭连接,注意如果是增删改方法需要手动提交事务后再关闭连接, MybatisUtil.closeSqlSession(sqlSession); System.out.println(users); } /* 结果 Page {count=true, pageNum=1, pageSize=2, startRow=0, endRow=2, total=4, pages=2, reasonable=false, pageSizeZero=false} [ User(id=7, name=李四, sex=男, age=28, height=185.0, weight=null, courses=null), User(id=8, name=李四, sex=男, age=28, height=185.0, weight=null, courses=null) ] */ count: pageNum:当前页的页码 pageSize:每页显示的条数 startRow:从第几行开始 endRow:从第几行结束 total:一共多少条数据 pages:一共有多少页 reasonable: pageSizeZero: /* 结果 PageInfo {pageNum=2, pageSize=2, size=2, startRow=3, endRow=4, total=5, pages=3, list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=5, pages=3, reasonable=false, pageSizeZero=false} [ User(id=9, name=李四, sex=男, age=28, height=185.0, weight=null, courses=null), > User(id=10, name=三大, sex=男, age=18, height=155.0, weight=null, courses=null) ], prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]} */ pageNum:当前页的页码 pageSize:每页显示的条数 size:当前页真实条数(最后一页不一定满足) startRow:从第几行开始 endRow:从第几行结束 total:一共多少条数据 pages:一共有多少页 list:表示上文Page prePage:上一页 nextPage:下一页 isFirstPage:是否是第一页 isLastPage:是否是最后一页 hasPreviousPage:是否有上一页 hasNextPage:是否有下一页 navigatePages:导航页的页数 navigateFirstPage:导航页的第一页的页码 navigateLastPage:导航页的最后一页的页码 navigatepageNums:导航分页的页码