文章目录
- Spring
- SpringMVC
- Mybatis
- SSM框架整合
Spring
一、Spring简介
1.1 Spring介绍
Spring框架是一个非常活跃的开源框架, 基于IOC和AOP来构架多层JavaEE系统,以帮助分离项目组件之间的依赖关系,它的主要目地是简化企业开发.
1.2 Spring解决的问题
- 方便解耦,简化开发:Spring 就是一个大工厂,可以将所有对象创建和依赖关系维护,交给 Spring 管理
- AOP 编程的支持:Spring 提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能
- 声明式事务的支持:只需要通过配置就可以完成对事务的管理,而无需手动编程
- 方便程序的测试:Spring 对 Junit4 支持,可以通过注解方便的测试 Spring 程序
- 方便集成各种优秀框架:spring是一个一站式的开源框架。Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz 等)的直接支持
- 降低 JavaEE API 的使用难度:Spring对 JavaEE 开发中非常难用的API(JDBC、JavaMail、远程调用等),都提供了封装,使这些 API 应用难度大大降低
1.3 Spring的组成
Spring框架包含的功能大约由20个模块组成。这些模块按组可分为核心容器、数据访问/集成,Web,AOP(面向切面编程)、设备、消息和测试
ORM:(Object relation mapping) 对象关系映射 操作数据库的。
Object :java对象 User Product实体类
relation:关系型数据库 数据库表
mapping:映射【xml配置文件】 通过映射把实体类和数据库表进行关联,达到操作实体类能够操作数据库表的目的。User类和user表进行关联。
hibernate就是一个标准的orm框架。
core - 核心模块
- spring-core:依赖注入IoC与DI的最基本实现
- spring-beans:Bean工厂与bean的装配
- spring-context:spring的context上下文即IoC容器
- spring-context-support
- spring-expression:spring表达式语言
详细说明
(1)spring-core
这个jar文件包含Spring框架基本的核心工具类,Spring其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类
(2)spring-beans
这个jar文件是所有应用都要用到的,它包含访问配置文件、创建和管理bean以及进行Inversion of Control / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的IoC/DI支持,引入spring-core.jar及spring- beans.jar文件就可以了
(3)spring-context
Spring核心提供了大量扩展,这样使得由 Core 和 Beans 提供的基础功能增强:这意味着Spring 工程能以框架模式访问对象。Context 模块继承了Beans 模块的特性并增加了对国际化(例如资源绑定)、事件传播、资源加载和context 透明化(例如 Servlet container)。同时,也支持JAVA EE 特性,例如 EJB、 JMX 和 基本的远程访问。Context 模块的关键是 ApplicationContext 接口。spring-context-support 则提供了对第三方库集成到 Spring-context 的支持,比如缓存(EhCache, Guava, JCache)、邮件(JavaMail)、调度(CommonJ, Quartz)、模板引擎(FreeMarker, JasperReports, Velocity)等。
(4)spring-expression
为在运行时查询和操作对象图提供了强大的表达式语言。它是JSP2.1规范中定义的统一表达式语言的扩展,支持 set 和 get 属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等。
数据访问与集成层包含 JDBC、ORM、OXM、JMS和事务模块。
(1)spring-jdbc
提供了 JDBC抽象层,它消除了冗长的 JDBC 编码和对数据库供应商特定错误代码的解析。
(2)spring-tx
支持编程式事务和声明式事务,可用于实现了特定接口的类和所有的 POJO 对象。编程式事务需要自己写beginTransaction()、commit()、rollback()等事务管理方法,声明式事务是通过注解或配置由 spring 自动处理,编程式事务粒度更细。
(3)spring-orm
提供了对流行的对象关系映射 API的集成,包括 JPA、JDO 和 Hibernate 等。通过此模块可以让这些 ORM 框架和 spring 的其它功能整合,比如前面提及的事务管理。
(4)spring-oxm
模块提供了对 OXM 实现的支持,比如JAXB、Castor、XML Beans、JiBX、XStream等。
(5)spring-jms
模块包含生产(produce)和消费(consume)消息的功能。从Spring 4.1开始,集成了 spring-messaging 模块
Spring 处理Web层jar
Web 层包括 spring-web、spring-webmvc、spring-websocket、spring-webmvc-portlet 等模块。
详细说明
(1)spring-web
提供面向 web 的基本功能和面向 web 的应用上下文,比如 multipart 文件上传功能、使用 Servlet 监听器初始化 IoC 容器等。它还包括 HTTP 客户端以及 Spring 远程调用中与 web 相关的部分
(2)spring-webmvc
为 web 应用提供了模型视图控制(MVC)和 REST Web 服务的实现。Spring 的 MVC 框架可以使领域模型代码和 web 表单完全地分离,且可以与 Spring 框架的其它所有功能进行集成
(3)spring-webmvc-portlet
(即Web-Portlet模块)提供了用于 Portlet 环境的 MVC 实现,并反映了 pring-webmvc 模块的功能
Spring AOP涉及jar
(1)spring-aop
提供了面向切面编程(AOP)的实现,可以定义诸如方法拦截器和切入点等,从而使实现功能的代码彻底的解耦。使用源码级的元数据。
(2)spring-aspects
提供了对 AspectJ 的集成
Instrumentation 模块涉及jar
(1)spring-instrument
模块提供了对检测类的支持和用于特定的应用服务器的类加载器的实现。
(2)spring-instrument-tomcat
模块包含了用于 Tomcat 的Spring 检测代理。
Messaging消息处理 涉及jar
spring-messaging 模块
从 Spring 4 开始集成,从一些 Spring 集成项目的关键抽象中提取出来的。这些项目包括 Message、MessageChannel、MessageHandler 和其它服务于消息处理的项目。这个模块也包含一系列的注解用于映射消息到方法
Test模块涉及jar
spring-test 模块
通过 JUnit 和 TestNG 组件支持单元测试和集成测试。它提供了一致性地加载和缓存 Spring 上下文,也提供了用于单独测试代码的模拟对象(mock object)
二、IOC:控制反转
(Inverse of Control) 就是指把创建对象的权利反转给spring容器。
-
引入spring-context和spring-context-support两个jar依赖
-
创建User类,Spring的配置文件
public class User { private Integer uid; private String name; private String password; public User() { } @Override public String toString() {......} getter() setter() }
<?xml version="1.0" encoding="UTF-8"?> <beans> <!-- id=是唯一的 通过id值可以找到该类的实例 class:类的全路径 默认使用无参数构造。 --> <bean id="user" class="ioc.User"/> </beans>
-
测试
public static void main(String[] args) { //ClassPathXmlApplicationContext使用该类去加载配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("aplicationContext.xml"); //获得springbean工厂管理 user类 User user=context.getBean("user", User.class); user.setName("jack"); System.out.println(user); }
三. 对象创建的细节
3.1 bean标签和属性讲解
bean标签:是根标签beans内部必须包含的标签,它是用于声明具体的类的对象!
bean标签对应的属性
Property | 属性解释 |
---|---|
class | 指定bean对应类的全路径 |
name | name是bean对应对象的一个标识 |
scope | 执行bean对象创建模式和生命周期 |
id | id是bean对象的唯一标识,不能添加特别字符 |
lazy-init | 是否延时加载 默认值:false |
init-method | 对象初始化方法 |
destroy-method | 对象销毁方法 |
<?xml version="1.0" encoding="UTF-8"?>
<beans ......>
<!--
name 可以重复,可以使用特殊字符
id是唯一的 通过id值可以找到该类的实例 不能使用特殊字符 作用和name几乎相同
class:类的全路径
默认使用无参构造
scope:默认是singleton单例的 如果想使用多例的:scope="prototype"
lazy-init:false 默认false 只对单例有效,设置单例时使用 加载配置文件就会创建该实例
lazy-init:true 延迟初始化,在用到对象的时候才会创建对象
init-method="init"配置初始化方法
destroy-method="des"配置销毁方法
-->
<bean name="user" id="user" class="cgp.model.User" scope="singleton" lazy-init="true" init-method="init" destroy-method="des"/>
<!--
静态工厂模式
class:工厂的类
factory-method:工厂的静态方法
-->
<bean id="user2" class="cgp.model.UserFactory" factory-method="getUser"/>
<!--
非静态工厂模式:
class:工厂的类
factory-method:工厂的静态方法
factory-bean:工厂的实例的id
-->
<bean id="fac" class="cgp.model.UserFactory"/>
<bean id="user3" factory-bean="fac" factory-method="getUser2"/>
</beans>
四、DI依赖注入
Dependency Injection,说的是创建对象实例时,同时为这个对象注入它所依赖的属性。相当于把每个bean与bean之间的关系交给容器管理。而这个容器就是spring。
案例步骤:
-
.准备spring的环境
-
UserController
UserService UserServiceImpl
UserDao UserDaoImpl
-
编写配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans ...> <!--把控制器交给spring--> <bean id="userController" class="di.UserController"> <!-- 1.一定要在UserController里面注入的属性提供getter和setter方法,默认使用setter方法注入。 name:setUserService方法名 去掉set首字母变小写 ref=引用其他bean的id --> <!--注入service--> <property name="userService" ref="userService"/> </bean> <!--把控制器交给spring--> <bean id="userService" class="di.UserServiceImpl"> <!--注入dao--> <property name="userDao" ref="userDao"/> </bean> <!--把控制器交给spring--> <bean id="userDao" class="di.UserDaoImpl"/> </beans>
-
测试代码
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("di/di.xml"); //获得springbean工厂管理 user 类 UserController uc= context.getBean("userController", UserController.class); uc.add(); }
4.1 set方法注入
4.1.1 基本类型值注入使用value
<bean name="person" class="cgp.model.Person">
<!-- value值为基本类型 -->
<property name="name" value="jack"/>
<property name="age" value="18"/>
</bean>
测试代码
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = context.getBean("person", Person.class);
System.out.println("person = " + person);//输出toString
}
4.1.2 引用类型值注入ref
利用ref属性给 person的car属性赋值
<bean name="person" class="cgp.model.Person">
<property name="name" value="jack"/>
<property name="age" value="18"/>
<property name="car" ref="car"/>
</bean>
<bean name="car" class="cgp.model.Car">
<property name="name" value="大众"/>
<property name="color" value="白色"/>
</bean>
测试代码同上
4.2 构造函数注入
4.2.1 单个有参构造方法注入
//在Person中创建有参构造函数
public Person(String name, Car car) {
this.name = name;
this.car = car;
System.out.println("Person的有参构造");
}
配置xml
<bean name="person" class="cgp.model.Person">
<constructor-arg name="name" value="chen"/>
<constructor-arg name="car" ref="car"/>
</bean>
<bean name="car" class="cgp.model.Car">
<property name="name" value="大众"/>
<property name="color" value="白色"/>
</bean>
测试代码同上,会调用Person的有参构造
4.2.2 index属性:按参数索引注入
参数名一致,但位置不一致时,使用index
public Person(String name, Car car) {
this.name = name;
this.car = car;
System.out.println("Person(String name, Car car)");
}
//这两个构造函数的参数名相同,位置不同
public Person(Car car,String name){
this.name = name;
this.car = car;
System.out.println("Person(Car car,String name)");
}
配置xml:使用index
确定调用哪个构造函数
<bean name="person" class="cgp.model.Person">
<constructor-arg name="name" value="chen" index="1"/>
<constructor-arg name="car" ref="car" index="0"/>
</bean>
<bean name="car" class="cgp.model.Car">
<property name="name" value="大众"/>
<property name="color" value="白色"/>
</bean>
测试代码同上,会调用Person的第二个有参构造
4.2.3 type属性:按参数类型注入
参数名和位置一致,但类型不一致时,使用type
public Person(Car car,String name){
this.name = name;
this.car = car;
System.out.println("Person(Car car,String name)");
}
public Person(Car car,Integer name){
this.name = name+"";
this.car = car;
System.out.println("Person(Car car,Integer name)");
}
配置:使用type
指定参数的类型
<bean name="person" class="cgp.model.Person">
<constructor-arg name="name" value="123" type="java.lang.Integer"/>
<constructor-arg name="car" ref="car"/>
</bean>
<bean name="car" class="cgp.model.Car">
<property name="name" value="大众"/>
<property name="color" value="白色"/>
</bean>
测试代码同上,会调用Person的Integer的有参构造
4.3 p名称空间注入
导入p名称空间:
使用p:属性名 完成注入,走set方法
-
基本类型值: p:属性名=“值”
-
引入类型值: P:属性名-ref=“bean名称”
配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--1.第一步配置文件中 添加命名空间p
xmlns:p="http://www.springframework.org/schema/p"
-->
<!--使用p命名空间进行赋值-->
<bean name="person" class="cgp.model.Person" p:name="chen" p:age="18" p:car-ref="car"/>
<bean name="car" class="cgp.model.Car">
<property name="name" value="大众"/>
<property name="color" value="白色"/>
</bean>
</beans>
测试代码同上,会调用Person的无参构造
4.4 spel注入
spring Expression Language:spring表达式语言
value="#{}"都是使用value:
{}里面写整形 #{100+100*2}
{}写字符串:#{‘Manaphy’}
方法调用:#{‘中华人民共和国’.substring(1,2)}
调用静态方法:#{T(类).方法}
调用对象的属性:#{user.name}
配置方式:
<bean name="car" class="cgp.model.Car">
<property name="name" value="大众"/>
<property name="color" value="白色"/>
</bean>
<!--利用spel引入car的属性-->
<bean name="person" class="cgp.model.Person" p:car-ref="car">
<property name="name" value="#{car.name}"/>
<property name="age" value="#{person.age}"/>
</bean>
<!--注入日期类型:默认支持 yyyy/MM/dd的日期格式的字符串转Date-->
<bean id="user" class="cgp.model.User">
<property name="date" value="#{'2012/12/12'}"/>
<property name="date1" value="#{new java.util.Date()}"/>
<!--spel支持运算-->
<property name="uid" value="#{100+12*2}"/>
<!--spel支持访问其他bean属性-->
<property name="name" value="#{car.name}"/>
<!--spel调用静态方法 T(类名).方法()-->
<property name="password" value="#{T(java.util.UUID).randomUUID().toString()}"/>
</bean>
4.5 复杂类型注入
public class TestCollection {
private Object[] arrs;
private List<Object> list;
private Map<String,Object> map;
private Properties properties;
public TestCollection() {
}
getter() setter()
@Override
public String toString() {...}
}
配置:
<bean name="car" class="cgp.model.Car">
<property name="name" value="大众"/>
<property name="color" value="白色"/>
</bean>
<bean name="testColl" class="cgp.model.TestCollection">
<!--数组变量注入-->
<property name="arrs">
<list>
<value>数组1</value>
<!--引入其他类型-->
<ref bean="car"/>
</list>
</property>
<!--集合变量赋值-->
<property name="list">
<list>
<value>集合1</value>
<!--集合变量内部包含集合-->
<list>
<value>集合中的集合1</value>
<value>集合中的集合2</value>
<value>集合中的集合3</value>
</list>
<ref bean="car"/>
</list>
</property>
<!--map赋值-->
<property name="map">
<map>
<entry key="car" value-ref="car"/>
<entry key="name" value="保时捷"/>
<entry key="age" value="11"/>
</map>
</property>
<!--properties赋值-->
<property name="properties">
<props>
<prop key="name">age</prop>
<prop key="age">111</prop>
</props>
</property>
</bean>
测试:
public class TestColl {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
TestCollection testColl = context.getBean("testColl", TestCollection.class);
System.out.println("testColl = " + testColl);
}
}
//testColl = TestCollection{arrs=[数组1, Car{name='大众', color='白色'}], list=[集合1, [集合中的集合1, 集合中的集合2, 集合中的集合3], Car{name='大众', color='白色'}], map={car=Car{name='大众', color='白色'}, name=保时捷, age=11}, properties={age=111, name=age}}
4.6 在spring的配置文件中加载
db.properties:
jdbc.username=root
jdbc.password=1111
jdbc.url=jdbc:mysql://localhost:3306/shop?serverTimezone=GMT%2B8
jdbc.driverclass=com.mysql.cj.jdbc.Driver
<context:property-placeholder location="classpath:conf/*.properties"/>
然后通过以下方式可以使用里面的值:
<property name="driverClassName" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"/>
五、使用注解
使用注解的方式完成IOC和DI.
使用一些spring给我们提供的注解来代替我们原来写的xml方式.我们真正开发的时候基本都是注解开发.
注解:比如:@Test , @Controller
导入相关的jar包
引入Context的约束,并配置注解扫描
<?xml version="1.0" encoding="UTF-8"?>
<!--较之前多了 xmlns:context -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<context:component-scan base-package="cgp.spring.bean">
</beans>
注解的使用
@Component("person")// <bean name="person" class="cgp.spring.bean.Person" />
public class Person {
private String name;
private Integer age;
private Car car;
public Person(){
System.out.println("无参数构造方法!");
}
//getter,setter,toString
}
5.1 类头部可用的注解
这几个注解的功能和@Component 一样。
@Service("person") // service层
@Controller("person") // controller层
@Repository("person") // dao层
5.2 类头部可用的注解
指定对象作用域
@Scope(scopeName="singleton") 单例 scopeName也可以换成value
@Scope(scopeName="prototype") 多例
5.3 注入属性value值
1.设置成员变量上:通过反射给变量赋值
@Value("name值")
private String name;
@Value(“name值”) 等同于 @Value(value=“name值”)
2.加在set方法上:通过set方法赋值
@Value("tom")
public void setName(String name){
this.name = name;
}
5.4 自动装配
- @Autowired
使用 @Autowired
自动装配对象类型的属性: 下面的Person中的Car使用了自动装配
//将Car定义成接口
@Component
public interface Car {
void log();
}
//Baoma实现Car
@Component
public class Baoma implements Car {
public void log() {
System.out.println("宝马");
}
}
//XianDai实现Car
@Component
public class XianDai implements Car {
public void log() {
System.out.println("现代");
}
}
装配类:
@Scope(scopeName = "prototype")
@Component("person")
public class Person {
@Value("name值")
private String name;
private Integer age;
@Autowired
private Car car; //自动装配 可以选择Car,如果Car是接口,找Car的实现类!
注意: 以上操作会出现一个问题,如果Car是接口,且Car只有一个实现类,那么@Autowired会自动将实现类装配给Person的car变量上,但是如果Car是接口,并且有两个以上实现类,那么自动装配就会报错,无法选择由哪个实现类赋值.所以需要配合另一个注释@Qualifier(“bean name”), 这个属性可以将@Autowired按类型赋值改成按bean名字赋值.
5.5 @Qualifier
- 如果匹配多个类型一致的对象,将无法选择具体注入哪一个对象
- 使用
@Qualifier()
注解告诉spring容器自动装配哪个名称的对象。
@Scope(scopeName = "prototype")
@Component("person")
public class Person {
@Value("name值")
private String name;
private Integer age;
@Autowired
@Qualifier("baoma") //指定实现类
private Car car; //自动装配 可以选择Car,如果Car是接口,找Car的实现类!
5.6 @Resource
@Resource 是java的注释,但是Spring框架支持,@Resource指定注入哪个名称的对象
@Resource(“name”) == @Autowired + @Qualifier(“name”)
@Resource(name="baoma")
private Car car;
5.7 初始化和销毁方法
初始化和销毁方法等同于配置文件添加的init-method和destroy-method功能,
例:Person类中init方法和destroy方法添加如下注解:
@PostConstruct
public void init(){
System.out.println("初始化方法");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法");
}
六、AOP面向切面编程
6.1 什么是AOP
1、在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2、AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码
3、 经典应用:事务管理、性能监视、安全检查、缓存 、日志等
4、 Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码对程序进行增强(不修改源码的情况下)
5、 AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入
6.2 AOP的底层实现
Srping框架的AOP技术底层也是采用的代理技术,代理的方式提供了两种
- 基于JDK的动态代理
必须是面向接口的,只有实现了具体接口的类才能生成代理对象
-
基于CGLIB动态代理
对于没有实现了接口的类,也可以产生代理,产生这个类的子类的方式
Spring的传统AOP中根据类是否实现接口,来采用不同的代理方式
- 如果实现类接口,使用JDK动态代理完成AOP
- 如果没有实现接口,采用CGLIB动态代理完成AOP
6.3 动态代理
6.3.1 手动实现
public interface Worker {
void workInDay(double money);
void workInNight(double money);
}
public class XJJ implements Worker{
@Override
public void workInDay(double money) {
System.out.println("上白班-->"+money);
}
@Override
public void workInNight(double money) {
System.out.println("上夜班-->"+money);
}
}
//手动实现
public class XJJProxy implements Worker{
private XJJ xjj;
public XJJProxy() {
this.xjj=new XJJ();
}
@Override
public void workInDay(double money) {
System.out.println("开启白班事物");
xjj.workInDay(money);
System.out.println("提交白班事物");
}
@Override
public void workInNight(double money) {
System.out.println("开启夜班事物");
xjj.workInNight(money);
System.out.println("提交夜班事物");
}
}
//测试
public class DemoXJJ {
public static void main(String[] args) {
XJJProxy xjjProxy=new XJJProxy();
xjjProxy.workInDay(1000);
xjjProxy.workInNight(1500);
}
}
6.3.2 JDK动态代理
public class DemoTest {
//jdk的代理 需要接口实现
public static void main(String[] args) {
/**
* 第一个参数:是类加载器
* 第二个参数:目标类的父接口数组
* 第三个参数:回调函数 当执行目标类的任意方法 都会走该方法
*/
Worker proxyInstance = (Worker)Proxy.newProxyInstance(XJJ.class.getClassLoader(), XJJ.class.getInterfaces(), new InvocationHandler() {
/**
*
* @param proxy 生成的代理类对象 一般不使用该对象
* @param method 目标类正在执行的方法对象
* @param args 目标类正在执行的方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "--->方法执行之前 记录日志 开启事物");
Object invoke = method.invoke(XJJ.class.newInstance(), (double) args[0] / 2);
System.out.println("--->方法执行之后 记录日志 提交事物");
return invoke;
}
});
proxyInstance.workInDay(1000);
proxyInstance.workInNight(1500);
}
}
6.3.3 CGLIB字节码增强
public class DemoCglib {
//cglib动态代理 基于子类
public static void main(String[] args) {
//1.获得代理类的核心类Enhancer对象
Enhancer enhancer = new Enhancer();
//2.设置父类(目标类),setSuperclass()方法,底层是创建目标类的子类
enhancer.setSuperclass(XJJ.class);
//3.设置回调函数enhancer.setCallback(new MethodInterceptor())
enhancer.setCallback(new MethodInterceptor() {
/**
*
* @param o 代理对象
* @param method 正在执行的目标方法对象
* @param objects 方法的实参
* @param methodProxy 方法得当代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("执行目标方法之前-->"+method.getName());
Object invoke = method.invoke(XJJ.class.newInstance(), (double)objects[0]/2);
//invoke就是方法的返回值
System.out.println("执行目标方法之后-->"+invoke);
return invoke;
}
});
//4.创建代理对象create()方法
XJJ o = (XJJ) enhancer.create();
//5.测试
o.workInDay(1000);
o.workInNight(1500);
}
}
6.4 AOP的开发中的相关术语
- Joinpoint(连接点) – 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
- Pointcut(切入点) – 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
- Advice(通知/增强) – 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能) 通知就定义了,需要做什么,以及在某个连接点的什么时候做。 上面的切点定义了在哪里做
- Introduction(引介) – 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field
- Target(目标对象) – 代理的目标对象
- Weaving(织入) – 是指把增强应用到目标对象来创建新的代理对象的过程
- Proxy(代理) – 一个类被AOP织入增强后,就产生一个结果代理类
- Aspect(切面) – 是切入点和通知的结合,以后咱们自己来编写和配置的
6.5 spring aop编程
public interface UserService {
int add();
void delete();
void update();
void query();
}
public class UserServiceImpl implements UserService {
@Override
public int add() {
System.out.println("add");
return 100;
}
@Override
public void delete() {
System.out.println("delete");
}
@Override
public void update() {
System.out.println("update");
}
@Override
public void query() {
System.out.println("query");
}
}
/**
* 环绕通知 org.aopalliance.intercept.MethodInterceptor
* 在目标方法执行前后实施增强
*/
public class MyAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) {
Object o = null;
try {
System.out.println("前置通知-->" + methodInvocation.getMethod().getName());
o = methodInvocation.proceed();//执行目标方法
System.out.println("后置通知-->" + o);//获得方法的返回值
} catch (Throwable throwable) {
System.out.println("异常通知-->");//回滚事物
throwable.printStackTrace();
} finally {
//提交事物,释放资源
System.out.println("最终通知-->");
return o;
}
}
}
<!--配置目标类-->
<bean id="userService" class="aop.service.impl.UserServiceImpl"/>
<!--配置通知类-->
<bean id="myAdvice" class="aop.MyAdvice"/>
<!--aop配置
proxy-target-class:默认会根据目标类有无接口来进行选用jdk的动态还是cglib动态代理,有接口选jdk的动态代理,无选择cglib动态代理。
指定true:强制使用cglib
-->
<aop:config proxy-target-class="true">
<!--配置切点表达式,根据该切点表达式可以找到你要拦截的类的方法
id;指定id
expression:切点表达式
execution(public * aop.service.impl.*.*(..))
public指方法的访问权限修饰符 默认public可以不写
*表示方法的返回值是任意的。int String void ....
aop.service.impl:指拦截的包路径
*:第一个*表示包下面的任意类
*:表示该包下面的任意方法
(..):表示方法的形参任意
-->
<aop:pointcut id="cut" expression="execution(* aop.service.impl.*.add*(..))"/>
<aop:pointcut id="cut2" expression="execution(* aop.service.impl.*.delete*(..))"/>
<!--配置切面
advice-ref:指向通知的id
pointcut-ref:指向切点表达式的id
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="cut"/>
<aop:advisor advice-ref="myAdvice" pointcut-ref="cut2"/>
</aop:config>
</beans>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:aop/spring-aop.xml")
public class DemoAop {
@Autowired
private UserService userService;
@Test
public void run(){
userService.add();
userService.delete();
userService.update();
userService.query();
}
}
6.6 AspectJ编程
6.6.1 AspectJ介绍
-
AspectJ是一个基于Java语言的AOP框架
-
Spring2.0以后新增了对AspectJ切点表达式支持
-
@AspectJ 是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面
-
主要用途:自定义开发
新版本Spring框架,建议使用AspectJ方式来开发AOP
6.6.2 AspectJ 通知类型
-
aop联盟定义通知类型,具有特性接口,必须实现,从而确定方法名称。
-
aspectj 通知类型,只定义类型名称。方法格式。
6.6.3 基于xml实现
编写切面类
//Aspectj AOP编程的通知只声明方法的形式 不需要接口实现
public class MyAdvice {
//前置通知
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知-->"+joinPoint.getSignature().getName());
}
//后置通知 获得方法的返回值
//Object res 第二个形参表示方法的返回值 参数名需要进行配置
public void afterReturing(JoinPoint joinPoint,Object res){
System.out.println(res+"后置通知-->"+joinPoint.getSignature().getName());
}
//异常通知
//Throwable e 表示异常对象 形参名需要配置
public void afterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println(e.getMessage()+"异常通知-->"+joinPoint.getSignature().getName());
}
//最终通知
public void after(){
System.out.println("最终通知-->");
}
//环绕通知
public Object around(ProceedingJoinPoint joinPoint){
Object proceed=null;
try {
System.out.println("around-->前置通知");
//手动执行目标方法
proceed = joinPoint.proceed();
System.out.println("around-->后置通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("around-->异常通知");
}finally {
System.out.println("around-->最终通知");
return proceed;
}
}
}
编写目标类
public interface UserService {
void add();
}
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("UserServiceImpl.add");
}
}
切面类 aop的配置
<bean id="userService" class="aspectj.service.impl.UserServiceImpl"/>
<bean id="myAdvice" class="aspectj.MyAdvice"/>
<!--aop配置-->
<aop:config>
<!--配置一个公共的切点表达式-->
<aop:pointcut id="cut" expression="execution(* aspectj.service.impl.*.*(..))"/>
<aop:aspect ref="myAdvice">
<!--<aop:before method="myBefore" pointcut-ref="cut"/>-->
<!--这里自己配置切点表达式-->
<!-- <aop:after-returning method="afterReturing" pointcut="execution(* aspectj.service.impl.*.*(..))" returning="res"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="cut" throwing="e"/>
<aop:after method="after" pointcut-ref="cut"/>-->
<aop:around method="around" pointcut-ref="cut"/>
</aop:aspect>
</aop:config>
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:aspectj/spring-aspectj.xml")
public class DemoApsectj {
@Autowired
private UserService userService;
@Test
public void run(){
userService.add();
}
}
环绕通知输出:
around-->前置通知
UserServiceImpl.add
around-->后置通知
around-->最终通知
6.6.4 基于注解
切面类 aop的配置
<!--开启注解扫描-->
<context:component-scan base-package="aspectj_anno"/>
<!--开启aop注解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
修改实现类使用注解
修改切面类
//Aspect 声明切面,修饰切面类,从而获得通知
@Component//<bean id="myAdvice" class="aspectj.MyAdvice"/>
@Aspect//<aop:aspect ref="myAdvice">
public class MyAdvice {
//配置一个公共的切点表达式
@Pointcut("execution(* aspectj_anno.service.impl.*.*(..))")
public void myCut(){}
//前置通知<aop:before method="myBefore" pointcut="execution(* aspectj_anno.service.impl.*.*(..))"/>
@Before("execution(* aspectj_anno.service.impl.*.*(..))")
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知-->"+joinPoint.getSignature().getName());
}
//后置通知 获得方法的返回值
//Object res 第二个形参表示方法的返回值 参数名需要进行配置
@AfterReturning(value = "execution(* aspectj_anno.service.impl.*.*(..))",returning = "res")
public void afterReturing(JoinPoint joinPoint,Object res){
System.out.println(res+"后置通知-->"+joinPoint.getSignature().getName());
}
//异常通知
//Throwable e 表示异常对象 形参名需要配置
@AfterThrowing(value="myCut()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println(e.getMessage()+"异常通知-->"+joinPoint.getSignature().getName());
}
//最终通知
@After("myCut()")
public void after(){
System.out.println("最终通知-->");
}
//环绕通知
@Around("myCut()")
public Object around(ProceedingJoinPoint joinPoint){
Object proceed=null;
try {
System.out.println("around-->前置通知");
//手动执行目标方法
proceed = joinPoint.proceed();
System.out.println("around-->后置通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("around-->异常通知");
}finally {
System.out.println("around-->最终通知");
return proceed;
}
}
}
测试类同上,结果如下
around-->前置通知
前置通知-->add
UserServiceImpl.add
around-->后置通知
around-->最终通知
最终通知-->
null后置通知-->add
七、事务
7.1 Spring JDBC 数据访问
Spring JDBC是Spring所提供的持久层技术,它的主要目标是降低使用JDBC API的门槛,以一种更直接,更简介,更简单的方式使用JDBC API, 在Spring JDBC里,仅需做那些与业务相关的DML操作,而将资源获取,Statment创建,资源释放以及异常处理等繁杂而乏味的工作交给Spring JDBC.
虽然ORM的框架已经成熟丰富,但是JDBC的灵活,直接的特性,依然让他拥有自己的用武之地,如在完全依赖查询模型动态产生查询语句的综合查询系统中,Hibernaye,MyBatis,JPA等框架都无法使用,这里JDBC是唯一的选择.
导入相关的包
7.2 JdbcTemplate的简单使用
public class DemoJdbcTemplate {
/**
* spring给我们提供了一个类:JdbcTemplate
* jdbcTemplate.queryForObject(); 返回对象
* jdbcTemplate.query(); 返回一个List
* jdbcTemplate.update(); 新增、修改、删除
*/
@Test
public void run() throws PropertyVetoException {
//创建c3p0链接池
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//设置必要的信息
dataSource.setJdbcUrl("jdbc:mysql:///mydb?serverTimezone=GMT%2B8");
dataSource.setUser("root");
dataSource.setPassword("1111");
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
//创建jdbcTemplate对象
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("insert into user(uname,upwd) values (?,?)", "chen", "1234");
}
}
7.3 使用Spring管理JdbcTemplate
1.创建User类
@Getter
@Setter
@ToString
public class User {
private int uid;
private String uname;
private String upwd;
private Date birthday;
}
2.创建UserDao接口
public interface UserDao {
int add(User user);//增
int deleteById(int uid);//删
int update(User user);//改
User queryById(int uid);//查
List<User> queryAll();//查询所有
int count();//查询条数
}
3.创建UserDaoImpl类
@Repository
public class UserDaoImpl implements UserDao {
@Autowired //自动装配
private JdbcTemplate jdbcTemplate;
@Override
public int add(User user) {
return jdbcTemplate.update("insert into user(uname,upwd) values (?,?)", user.getUname(), user.getUpwd());
}
@Override
public int deleteById(int uid) {
return jdbcTemplate.update("delete from user where uid=?", uid);
}
@Override
public int update(User user) {
return jdbcTemplate.update("update user set uname=? where uid=?", user.getUname(), user.getUid());
}
/**
* BeanPropertyRowMapper 可以把结果集封装到对象里面
* 通过属性名和列名一致来调用setter进行设置值。
*/
@Override
public User queryById(int uid) {
return jdbcTemplate.queryForObject("select * from user where uid=?",
new BeanPropertyRowMapper<>(User.class),uid);
}
@Override
public List<User> queryAll() {
return jdbcTemplate.query("select * from user",new BeanPropertyRowMapper<>(User.class));
}
@Override
public int count() {
return jdbcTemplate.queryForObject("select count(uid) from user",Integer.class);
}
}
4.配置spring-jdbc.xml
<!--加载db.properties-->
<context:property-placeholder location="classpath:db.properties"/>
<!--开启注解扫描-->
<context:component-scan base-package="jdbctemplate2"/>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
p:user="${jdbc.user}"
p:password="${jdbc.password}"
p:jdbcUrl="${jdbc.url}"
p:driverClass="${jdbc.driverClass}"/>
<!--配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
5.测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:jdbctemplate2/spring-jdbc.xml")
public class DemoJdbcTemplate {
@Autowired
private UserDao userDao;
@Test
public void runAdd(){
User user=new User();
user.setUname("chen");
user.setUpwd("1213");
int row = userDao.add(user);
System.out.println("row = " + row);
}
@Test
public void runDelete(){
int row = userDao.deleteById(5);
System.out.println("row = " + row);
}
@Test
public void runUpdate(){
User user=new User();
user.setUid(3);
user.setUname("chen");
int row = userDao.update(user);
System.out.println("row = " + row);
}
@Test
public void runQueryObject(){
User user = userDao.queryById(1);
System.out.println("user = " + user);
}
@Test
public void runQueryAll(){
List<User> users = userDao.queryAll();
System.out.println(users);
}
@Test
public void runQueryCount(){
System.out.println(userDao.count());
}
}
使用JdbcDaoSupport,可以让我们的dao继承JdbcDaoSupport,然后注入DataSource或者JdbcTemplate,我们可以抽取一个BaseDao父类:让我们的dao类继承 BaseDao
@Component
public class BaseDao extends JdbcDaoSupport {
@Autowired
private DataSource dataSource;
//在该类初始化的时候,给父类设置dataSource
@PostConstruct
public void init(){
System.out.println("BaseDao.init");
super.setDataSource(dataSource);
}
}
@Repository
public class UserDaoImpl2 extends BaseDao implements UserDao {
//JdbcDaoSupport是Spring给我们提供的一个类,我们可以让我们的dao类继承该类 然后给该类输入数据源或者模板类
@Override
public int add(User user) {
return this.getJdbcTemplate().update("insert into user(uname,upwd) values (?,?)", user.getUname(), user.getUpwd());
}
@Override
public int deleteById(int uid) {
return this.getJdbcTemplate().update("delete from user where uid=?", uid);
}
@Override
public int update(User user) {
return this.getJdbcTemplate().update("update user set uname=? where uid=?", user.getUname(), user.getUid());
}
@Override
public User queryById(int uid) {
return this.getJdbcTemplate().queryForObject("select * from user where uid=?",
new BeanPropertyRowMapper<>(User.class),uid);
}
@Override
public List<User> queryAll() {
return this.getJdbcTemplate().query("select * from user",new BeanPropertyRowMapper<>(User.class));
}
@Override
public int count() {
return this.getJdbcTemplate().queryForObject("select count(uid) from user",Integer.class);
}
}
配置spring-jdbc.xml
<context:property-placeholder location="db.properties"/>
<context:component-scan base-package="jdbctemplate2"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
p:user="${jdbc.user}"
p:password="${jdbc.password}"
p:jdbcUrl="${jdbc.url}"
p:driverClass="${jdbc.driverClass}"/>
测试类同上
7.4 Spring事务管理
事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:(一般只需掌握前两种)
-
PROPAGATION_REQUIRED
表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务 -
PROPAGATION_SUPPORTS
表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行 -
PROPAGATION_MANDATORY
表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 -
PROPAGATION_REQUIRED_NEW
表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起 -
PROPAGATION_NOT_SUPPORTED
表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。 -
PROPAGATION_NEVER
表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 -
PROPAGATION_NESTED
表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务
事务的特性
ACID
1 - 原子性(atomicity)
事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。
2、一致性(consistency)
事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。
3、隔离性(isolation)
一个事务的执行不能被其他事务所影响。企业级的数据库每一秒钟都可能应付成千上万的并发访问,因而带来了并发控制的问题。由数据库理论可知,由于并发访问,在不可预料的时刻可能引发如下几个可以预料的问题:(见“二、事务的并发问题“)
4、持久性(durability)
一个事务一旦提交,事物的操作便永久性的保存在DB中。即使此时再执行回滚操作也不能撤消所做的更改
事务的并发问题
1、脏读(Dirty Read)
一个事务读取到了另一个事务未提交的数据操作结果。这是相当危险的,因为很可能所有的操作都被回滚。
2、不可重复读(虚读)(NonRepeatable Read)
一个事务对同一行数据重复读取两次,但是却得到了不同的结果。例如事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。
3、幻读(Phantom Read)
事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据,这是因为在两次查询过程中有另外一个事务插入数据造成的
事务的隔离级别
1、读未提交 Read uncommitted:最低级别,以上情况均无法保证。
2、读已提交 Read committed:可避免脏读情况发生。
4、可重复读 Repeatable read:可避免脏读、不可重复读情况的发生。不可以避免幻读。
8、串行化读 Serializable:事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重
7.4.1 转账案例
1.项目名:spring-tx 引入jar包 复制数据库配置db.properties
2.创建数据库
3.创建dao接口
public interface AccountDao {
int descMoney(int from,double money);
int addMoney(int to,double money);
}
4.创建dao实现类
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int descMoney(int from, double money) {
String sql="update account set money=money-? where id=?";
return jdbcTemplate.update(sql,money,from);
}
@Override
public int addMoney(int to, double money) {
String sql="update account set money=money+? where id=?";
return jdbcTemplate.update(sql,money,to);
}
}
5.创建service接口
public interface AccountService {//转账业务
boolean transfer(int from,int to,double money);
}
6.创建service实现类
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public boolean transfer(Integer from, Integer to, Double money) {
accountDao.descMoney(from,money);
accountDao.addMoney(to,money);
return true;
}
}
7.添加ioc配置文件
<?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:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx" 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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:property-placeholder location="db.properties"/>
<context:component-scan base-package="spring_tx"/>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
p:user="${jdbc.user}"
p:password="${jdbc.password}"
p:jdbcUrl="${jdbc.url}"
p:driverClass="${jdbc.driverClass}"/>
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--1.配置平台事物管理器
DataSourceTransactionManager 用于 JDBC和mybatis 的事务管理
HibernateTransactionManager 用于 Hibernate 的事务管理
JpaTransactionManager 用于 Jpa 的事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2.配置事务详情/属性 (传播行为 超时时间 是否可读)-->
<!--配置事务通知/属性:transaction-manager="transactionManager":指向事务平台管理器-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务属性-->
<tx:attributes>
<!--
tx:method配置事务方法
propagation:事务的传播行为 一般增删改配置REQUIRED 查询配置SUPPORTS
isolation:隔离级别 一般默认不配置
timeout="-1" 默认-1 采取数据库默认的超时时间 一般默认不配置
read-only="false":是否只读 默认false 查询配置true
no-rollback-for:遇到什么异常不回滚 一般默认不配置
rollback-for:遇到什么异常回滚 一般默认不配置
name:配置的方法名 采取通配符的方式 配置业务层的方法名 事务在业务层开启
-->
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="modify*" propagation="REQUIRED"/>
<tx:method name="trans*" propagation="REQUIRED"/>
<tx:method name="change*" propagation="REQUIRED"/>
<tx:method name="query*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--3.aop编程-->
<aop:config>
<aop:pointcut id="cut" expression="execution(* spring_tx.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="cut"/>
</aop:config>
</beans>
8.测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring_tx/spring-tx.xml")
public class DemoTransfer {
@Autowired
private AccountService accountService;
@Test
public void run(){
boolean transfer = accountService.transfer(1, 2, (double) 500);
System.out.println("transfer = " + transfer);
}
}
7.4.2 Spring XML配置声明事务
TransactionManager
在不同平台,操作事务的代码各不相同,因此spring提供了一个 TransactionManager 接口:
-
DataSourceTransactionManager 用于 JDBC和mybatis 的事务管理
-
HibernateTransactionManager 用于 Hibernate 的事务管理
-
JpaTransactionManager 用于 Jpa 的事务管理
接口的定义
事务的属性介绍:这里定义了传播行为、隔离级别、超时时间、是否只读
package org.springframework.transaction;
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0; //支持当前事务,如果不存在,就新建一个
int PROPAGATION_SUPPORTS = 1; //支持当前事务,如果不存在,就不使用事务
int PROPAGATION_MANDATORY = 2; //支持当前事务,如果不存在,就抛出异常
int PROPAGATION_REQUIRES_NEW = 3;//如果有事务存在,挂起当前事务,创建一个新的事物
int PROPAGATION_NOT_SUPPORTED = 4;//以非事务方式运行,如果有事务存在,挂起当前事务
int PROPAGATION_NEVER = 5;//以非事务方式运行,如果有事务存在,就抛出异常
int PROPAGATION_NESTED = 6;//如果有事务存在,则嵌套事务执行
int ISOLATION_DEFAULT = -1;//默认级别,MYSQL: 默认为REPEATABLE_READ级别 SQLSERVER: 默认为READ_COMMITTED
int ISOLATION_READ_UNCOMMITTED = 1;//读取未提交数据(会出现脏读, 不可重复读) 基本不使用
int ISOLATION_READ_COMMITTED = 2;//读取已提交数据(会出现不可重复读和幻读)
int ISOLATION_REPEATABLE_READ = 4;//可重复读(会出现幻读)
int ISOLATION_SERIALIZABLE = 8;//串行化
int TIMEOUT_DEFAULT = -1;//默认是-1,不超时,单位是秒
//事务的传播行为
int getPropagationBehavior();
//事务的隔离级别
int getIsolationLevel();
//事务超时时间
int getTimeout();
//是否只读
boolean isReadOnly();
String getName();
}
7.4.3 编程式的事务管理(了解)
7.5.4 使用注解方式添加事务
修改service实现类添加@Transactional注解
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
@Transactional//既可以写在方法上 也可以写在类上 写在类上表示该类的所有方法都加事务
// @Transactional(rollbackFor = {Exception.class})//阿里规范写法,需指定rollbackFor
// @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,timeout = -1,readOnly = false,
// rollbackFor = {Exception.class},noRollbackFor = {})
public boolean transfer(Integer from, Integer to, Double money) {
accountDao.descMoney(from,money);
accountDao.addMoney(to,money);
return true;
}
}
配置xml
<context:property-placeholder location="db.properties"/>
<context:component-scan base-package="spring_tx_anno"/>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
p:user="${jdbc.user}"
p:password="${jdbc.password}"
p:jdbcUrl="${jdbc.url}"
p:driverClass="${jdbc.driverClass}"/>
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置平台事物管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
测试类同上
附录:Spring模块介绍
GroupId | ArtifactId | 说明 |
---|---|---|
org.springframework | spring-beans | Beans 支持,包含 Groovy |
org.springframework | spring-aop | 基于代理的AOP支持 |
org.springframework | spring-aspects | 基于AspectJ 的切面 |
org.springframework | spring-context | 应用上下文运行时,包括调度和远程抽象 |
org.springframework | spring-context-support | 支持将常见的第三方类库集成到 Spring 应用上下文 |
org.springframework | spring-core | 其他模块所依赖的核心模块 |
org.springframework | spring-expression | Spring 表达式语言,SpEL |
org.springframework | spring-instrument | JVM 引导的仪表(监测器)代理 |
org.springframework | spring-instrument-tomcat | Tomcat 的仪表(监测器)代理 |
org.springframework | spring-jdbc | 支持包括数据源设置和 JDBC 访问支持 |
org.springframework | spring-jms | 支持包括发送/接收JMS消息的助手类 |
org.springframework | spring-messaging | 对消息架构和协议的支持 |
org.springframework | spring-orm | 对象/关系映射,包括对 JPA 和 Hibernate 的支持 |
org.springframework | spring-oxm | 对象/XML 映射(Object/XML Mapping,OXM) |
org.springframework | spring-test | 单元测试和集成测试支持组件 |
org.springframework | spring-tx | 事务基础组件,包括对 DAO 的支持及 JCA 的集成 |
org.springframework | spring-web | web支持包,包括客户端及web远程调用 |
org.springframework | spring-webmvc | REST web 服务及 web 应用的 MVC 实现 |
org.springframework | spring-webmvc-portlet | 用于 Portlet 环境的MVC实现 |
org.springframework | spring-websocket | WebSocket 和 SockJS 实现,包括对 STOMP 的支持 |
1、core - 核心模块
- spring-core:依赖注入IoC与DI的最基本实现
- spring-beans:Bean工厂与bean的装配
- spring-context:spring的context上下文即IoC容器
- spring-context-support
- spring-expression:spring表达式语言
详细说明
(1)spring-core
这个jar文件包含Spring框架基本的核心工具类,Spring其它组件要都要使用到这个包里的类,是其它组件的基本核心,当然你也可以在自己的应用系统中使用这些工具类
(2)spring-beans
这个jar文件是所有应用都要用到的,它包含访问配置文件、创建和管理bean以及进行Inversion of Control / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的IoC/DI支持,引入spring-core.jar及spring- beans.jar文件就可以了
(3)spring-context
Spring核心提供了大量扩展,这样使得由 Core 和 Beans 提供的基础功能增强:这意味着Spring 工程能以框架模式访问对象。Context 模块继承了Beans 模块的特性并增加了对国际化(例如资源绑定)、事件传播、资源加载和context 透明化(例如 Servlet container)。同时,也支持JAVA EE 特性,例如 EJB、 JMX 和 基本的远程访问。Context 模块的关键是 ApplicationContext 接口。spring-context-support 则提供了对第三方库集成到 Spring-context 的支持,比如缓存(EhCache, Guava, JCache)、邮件(JavaMail)、调度(CommonJ, Quartz)、模板引擎(FreeMarker, JasperReports, Velocity)等。
(4)spring-expression
为在运行时查询和操作对象图提供了强大的表达式语言。它是JSP2.1规范中定义的统一表达式语言的扩展,支持 set 和 get 属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等。
2、Data Access/Integration - 数据访问与集成
数据访问与集成层包含 JDBC、ORM、OXM、JMS和事务模块。
详细说明
(1)spring-jdbc
提供了 JDBC抽象层,它消除了冗长的 JDBC 编码和对数据库供应商特定错误代码的解析。
(2)spring-tx
支持编程式事务和声明式事务,可用于实现了特定接口的类和所有的 POJO 对象。编程式事务需要自己写beginTransaction()、commit()、rollback()等事务管理方法,声明式事务是通过注解或配置由 spring 自动处理,编程式事务粒度更细。
(3)spring-orm
提供了对流行的对象关系映射 API的集成,包括 JPA、JDO 和 Hibernate 等。通过此模块可以让这些 ORM 框架和 spring 的其它功能整合,比如前面提及的事务管理。
(4)spring-oxm
模块提供了对 OXM 实现的支持,比如JAXB、Castor、XML Beans、JiBX、XStream等。
(5)spring-jms
模块包含生产(produce)和消费(consume)消息的功能。从Spring 4.1开始,集成了 spring-messaging 模块
3、Web
Web 层包括 spring-web、spring-webmvc、spring-websocket、spring-webmvc-portlet 等模块。
详细说明
(1)spring-web
提供面向 web 的基本功能和面向 web 的应用上下文,比如 multipart 文件上传功能、使用 Servlet 监听器初始化 IoC 容器等。它还包括 HTTP 客户端以及 Spring 远程调用中与 web 相关的部分
(2)spring-webmvc
为 web 应用提供了模型视图控制(MVC)和 REST Web 服务的实现。Spring 的 MVC 框架可以使领域模型代码和 web 表单完全地分离,且可以与 Spring 框架的其它所有功能进行集成
(3)spring-webmvc-portlet
(即Web-Portlet模块)提供了用于 Portlet 环境的 MVC 实现,并反映了 pring-webmvc 模块的功能
4、AOP
(1)spring-aop
提供了面向切面编程(AOP)的实现,可以定义诸如方法拦截器和切入点等,从而使实现功能的代码彻底的解耦。使用源码级的元数据。
(2)spring-aspects
提供了对 AspectJ 的集成
5、Instrumentation
(1)spring-instrument
模块提供了对检测类的支持和用于特定的应用服务器的类加载器的实现。
(2)spring-instrument-tomcat
模块包含了用于 Tomcat 的Spring 检测代理。
6、Messaging - 消息处理
spring-messaging 模块
从 Spring 4 开始集成,从一些 Spring 集成项目的关键抽象中提取出来的。这些项目包括 Message、MessageChannel、MessageHandler 和其它服务于消息处理的项目。这个模块也包含一系列的注解用于映射消息到方法
7、Test
spring-test 模块
通过 JUnit 和 TestNG 组件支持单元测试和集成测试。它提供了一致性地加载和缓存 Spring 上下文,也提供了用于单独测试代码的模拟对象(mock object)
SpringMVC
一.Spring MVC 简介
大部分Java应用都是Web应用,展现层是WEB应用不可忽略的重要环节.Spring为了展现层提供了一个优秀的WEB框架-Spring MVC . 和众多的其他WEB框架一样,它基于MVC的设计理念. 此外,它采用了松散耦合,可插拔的组件结构,比其他的MVC框架更具有扩展性和灵活性,Spring MVC通过一套MVC注解,让POJO成为成为处理请求的处理器,无须实现任何接口.同时,Spring MVC还支持REST风格的URL请求:注解驱动及REST风格的Spring MVC是Spring的出色功能之一.
此外,Spring MVC在数据绑定,视图解析,本地化处理及静态资源处理上都有许多不俗的表现,它在框架设计,可扩展,灵活性等方面全面超越了Struts,WebWork等MVC框架,从原来的追赶者一跃成为了MVC的领跑者.
1.1 MVC模式简介
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。
MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式:[1]
- Model(模型)表示应用程序核心(比如数据库记录列表)。
- View(视图)显示数据(数据库记录)。
- Controller(控制器)处理输入(写入数据库记录)。
MVC 模式同时提供了对 HTML、CSS 和 JavaScript 的完全控制。
**Model(模型)**是应用程序中用于处理应用程序数据逻辑的部分。
通常模型对象负责在数据库中存取数据。
**View(视图)**是应用程序中处理数据显示的部分。
通常视图是依据模型数据创建的。
**Controller(控制器)**是应用程序中处理用户交互的部分。
通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
MVC 分层有助于管理复杂的应用程序,因为您可以在一个时间内专门关注一个方面。例如,您可以在不依赖业务逻辑的情况下专注于视图设计。同时也让应用程序的测试更加容易。
MVC 分层同时也简化了分组开发。不同的开发人员可同时开发视图、控制器逻辑和业务逻辑。
1.2 Spring MVC 体系结构
Spring MVC是基于 Model 2实现的技术框架,Model 2是经典的MVC(model,view,control)模型在WEB应用中的变体.这个改变主要源于HTTP协议的无状态性,Model 2 的目的和MVC一样,也是利用处理器分离模型,视图和控制,达到不同技术层级间松散层耦合的效果,提高系统灵活性,复用性和可维护性.大多情况下,可以将Model 2 与 MVC等同起来.
Spring MVC体系概述
Spring MVC框架围绕DispatcherServlet这个核心展开,DispatcherServlet是Spring MVC的总导演,总策划.它负责截获请求并将其分派给相应的处理器处理.Spring MVC框架包括注解驱动控制器,请求及响应的信息处理,视图解析,本地化解析,上传文件解析,异常处理及表单标签绑定内容等…
Spring核心组件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-av6O08Q1-1575040207827)(images/mvc-context-hierarchy.png)]
组件介绍
DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。
HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器,调用处理器传递参数等工作!
ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。
1.3 Spring MVC执行流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cH9VrPNm-1575040207828)(images/springmvc支持流程图.png)]
从接收请求到响应,Spring MVC框架的众多组件通力配合,各司其职,有条不紊的完成分内工作!在整个框架中,DispatchserServlet处于核心的位置,它负责协调和组织不同组件以完成请求处理以及返回影响工作.和大多数Web MVC框架一样,Spring MVC 通过一个前段的Servlet接收所有请求,并将这些工作委托给其他组件进行处理,DispatcherServlet 就是Spring MVC的前段Servlet。下面对Spring MVC处理请求的整体过程进行详解!
- 整个过程始于客户端发出的一个HTTP请求,WEB应用服务器接收到这个请求,如果匹配DispatcherServlet的映请求映射路径(web.xml中指定),则Web容器将该请求转交给DispatcherServlet处理。
- 接收到这个请求后,将根据请求的信息(包括 URL,HTTP方法,请求头,请求参数,Cookie等)及HandlerMapping的配置找到处理请求的处理器(Handler)。可将HandlerMapping看做路由控制器,将Handler看做目标主机.值得注意的是,在Spring MVC中并没有定义一个Handler接口,实际上,任何一个Object都可以成为请求处理器。
- 当DispatcherServlet根据HandlerMapping得到对应当前请求的Handler后,通过HandlerAdapter对Handler的封装,再以统一的适配器接口调用Handler。HandlerAdapter是Spring MVC的框架级接口,顾名思义,HandlerAdapter是一个适配器,它用统一的接口对各种Handler的方法进行调用.
- 处理器完成业务逻辑的处理后将返回一个ModelAndView给DispatcherServlet,ModelAndView包含了视图逻辑名和模型数据信息。
- ModelAndView中包含的是"逻辑视图名"而并非真正的视图对象,DispatcherServlet借由ViewResolver完成逻辑视图名到真实视图对象的解析工作。
- 当得到真实的视图对象View后,DispatcherServlet就使用这个View对象对ModelAndView中的模型数据进行视图渲染。
- 最终客户端得到的响应信息可能是一个普通的HTML页面,也可能是一个XML或者JSON串,甚至是一张图片或者一个PDF文档等不同的媒体形式。
1.4 组件开发实现情况
1、前端控制器DispatcherServlet(不需要工程师开发),由框架提供
作用:接收请求,响应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。需要在web.xml里面进行配置。
用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
2、处理器映射器HandlerMapping(不需要工程师开发),由框架提供
作用:根据的url来和注解或者xml进行匹配来找到目标handler.
找到以后返回给前端控制器一个执行器链(包括目标handler和拦截器)。找不到报错404.需要在spirng的配置文件里面进行配置。
3、处理器适配器HandlerAdapter(不需要工程师开发)
作用:对目标handler进行适配。并且去执行目标handler,执行完成之后,返回一个ModelAdnView。
需要进行配置。在spirng的配置文件里面进行配置。
4、处理器Handler(需要工程师开发)
handler就是我们以前写的servlet程序。handler需要我们自己编写,提供相应的注解。
5、视图解析器View resolver(不需要工程师开发),由框架提供
把逻辑视图解析成真正的视图。返回给前端处理器。springmvc提供,需要进行配置,在spirng的配置文件里面。
6、视图ModelAdnView(需要工程师开发jsp…)
包含了数据和要跳转的页面。里面的视图是逻辑视图,还需要进行处理才能确定真正的视图。 springmvc提供,直接使用。
1.5 核心分发器DispatcherServlet
DispatcherServlet是Spring MVC的"灵魂"和"心脏",它负责接受HTTP请求并协调 Spring MVC的各个组件完成请求处理的工作。和任何Servlet一样,用户必须在web.xml中配置好DispatcherServlet。
1.5.1 DispatcherServlet介绍
DispatcherServlet是前端控制器设计模式的实现,提供spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处。
1.5.2 DispatcherServlet主要职责
DispatcherServlet主要用作职责调度工作,本身主要用于控制流程,主要职责如下:
- 文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析;
- 通过HandlerMapping,将请求映射到处理器(返回一个HandlerExecutionChain,它包括一个处理器、多个HandlerInterceptor拦截器);
- 通过HandlerAdapter支持多种类型的处理器(HandlerExecutionChain中的处理器);
- 通过ViewResolver解析逻辑视图名到具体视图实现;
- 本地化解析;
- 渲染具体的视图等;
- 如果执行过程中遇到异常将交给HandlerExceptionResolver来解析。
1.5.3 DispatcherServlet辅助类
spring中的DispatcherServlet使用一些特殊的bean来处理request请求和渲染合适的视图。这些bean就是Spring MVC中的一部分。你能够通过在WebApplicationContext中的一个或多个配置来使用这些特殊的bean。但是,你不需要在Spring MVC在维护这些默认要使用的bean时,去把那些没有配置过的bean都去初始化一道。在下一部分中,首先让我们看看在DispatcherServlet依赖的那些特殊bean类型
bean类型 | 说明 |
---|---|
Controlle | 处理器/页面控制器,做的是MVC中的C的事情,但控制逻辑转移到前端控制器了,用于对请求进行处理 |
HandlerMapping | 请求到处理器的映射,如果映射成功返回一个HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象;如BeanNameUrlHandlerMapping将URL与Bean名字映射,映射成功的Bean就是此处的处理器 |
HandlerAdapter | HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;如SimpleControllerHandlerAdapter将对实现了Controller接口的Bean进行适配,并且掉处理器的handleRequest方法进行功能处理 |
HandlerExceptionResolver处理器异常解析器 | 处理器异常解析,可以将异常映射到相应的统一错误界面,从而显示用户友好的界面(而不是给用户看到具体的错误信息) |
ViewResolver视图解析器 | ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;如InternalResourceViewResolver将逻辑视图名映射为jsp视图 |
LocaleResolver & LocaleContextResolver地区解析器和地区Context解析器 | 解析客户端中使用的地区和时区,用来提供不同的国际化的view视图。 |
ThemeResolver | 主题解析器,解析web应用中能够使用的主题,比如提供个性化的网页布局。 |
MultipartResolver | 多部件解析器,主要处理multi-part(多部件)request请求,例如:在HTML表格中处理文件上传。 |
FlashMapManager | FlashMap管理器储存并检索在"input"和"output"的FlashMap中可以在request请求间(通常是通过重定向)传递属性的FlashMap, |
二、基于SpringMVC 的WEB应用
项目名: spring-mvc-base
maven项目pom配置:
<?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>cgp</groupId>
<artifactId>spring-mvc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>spring-mvc Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.*</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>91</port>
<path>/web</path>
<uriEncoding>UTF-8</uriEncoding>
<server>tomcat7</server>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.1 配置web.xml
<servlet>
<!--配置springmvc前端处理器DispatcherServlet-->
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--用来加载springmvc的配置文件-->
<!--如果不配置,会默认加载/WEB-INF/<servlet-name>-servlet.xml文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!--在服务器启动的时候就初始化该servlet-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--配置拦截路径
/* --拦截所有请求 js jsp html css img 控制器等 一般不使用
/ --不拦截jsp js HTML css 控制器等 强烈推荐
*.action *.do --只拦截指定后缀的请求 传统项目 crm 管理系统
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--解决post请求乱码-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
param-name | param-value |
---|---|
contextConfigLocation | 引入springmvc配置文件,默认classpath:-servlet.xml,如果放在src/resources的下级文件夹,例如:resources/spring/servlet-name-servlet.xml,值可以编写成: classpath:/spring/servlet-name-servlet.xml,如果没有放在src/resources资源的根目录下,放在了WEB项目的WEB-INF/spring/servlet-name-serlvet.xml,值可以编写成:/WEB-INF/spring/servlet-name-servlet.xml。 |
namespace | 修改DispatcherServlet对应的命名空间,默认为-servlet,可以通过namespace修改默认名字! |
publishContext | 布尔类型的属性,默认值为ture,DispatcherServlet根据该属性决定是否将WebApplicationContext发布到ServletContext的属性列表中,以便调用者可以借用ServletContext找到WebApplicationContext实例,对应的属性名为DispatcherServlet#getServletContextAttributeName()方法返回的值。 |
publishEvents | 布尔类型的属性,当DispatcherServlet处理完一个请求后,是否需要向容器发布一个ServletRequestHandledEvent事件,默认值为true.如果容器中没有任何事件监听器,则可以将该属性设置为false,以便提高运行性能。 |
2.2 配置Spring MVC配置文件
<context:component-scan base-package="springmvc1.controller"/>
<!--配置处理器映射器-->
<!--<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>-->
<!--配置处理器适配器-->
<!--<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>-->
<!--MVC注解驱动 相当于以上两句 而且功能更强大-->
<mvc:annotation-driven/>
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置前缀和后缀-->
<property name="suffix" value=".jsp"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
</bean>
2.3 创建controller包以及控制器类
@Controller
public class HelloController {
@RequestMapping("/hello") //相当于接收:http://ip:port/应用名/hello
public String hello(){
System.out.println("hello-->");
//表示转发到页面 index是逻辑视图 加上前缀和后缀才是真正的视图 /WEB-INF/jsp/index.jsp
return "index";
}
}
2.4 测试Spring MVC
发布项目,通过浏览器,访问 当前项目对应地址+ /hello即可!
三、 常用注解
@RequestMapping注解应用
3.1.1 value属性
@Controller
@RequestMapping("cgp")//写在类上表示窄化请求,可不写
//@RequestMapping(value = "cgp")//方式二 value在只有一个参数的时候可不写
//@RequestMapping(value = {"cgp1","cgp2"})//方式三 可以写数组
public class Annotation {
@RequestMapping("add") //窄化后路径为http://ip:port/项目根路径/cgp/add
public String add(){
System.out.println("Annotation.add");
return "index";
}
}
3.1.2 method属性
可以指定方法对应的请求方式!如果客户端请求的方式和方法设置的方式不同,请求不成功!
//method属性表示该方法支持的请求类型,不写的话任意请求都可以。常用四种请求方式:GET POST PUT DELETE
@RequestMapping(value = "add2",method = RequestMethod.GET)
//@RequestMapping(value = "add2",method = RequestMethod.POST)
//@RequestMapping(value = "add2",method = {RequestMethod.GET,RequestMethod.POST})
public String add2(){
System.out.println("Annotation.add2");
return "index";
}
3.1.3 表单参数处理
- 创建一个登陆表单
<!--action指定controller中对应的方法路径即可!-->
<form action="/xx/login" method="POST">
<label for="username">用户名:<input type="text" name="username" /></label>
<label for="password">密码:<input type="text" name="password" /></label>
<input type="submit" value="登陆">
</form>
- 获取参数的控制器
@RequestMapping("login")
//只需要在方法的形参里面写上参数,参数的名字要和表单的name属性值一样。
public String login(String username,String password){
System.out.println("username = " + username);
System.out.println("password = " + password);
return "index";
}
- 获取特殊格式数据
//[username=chen&password=112&age=18&date=2012/12/12&hb=apple&hb=banana]
@RequestMapping("login")
//默认String字符串转化为Integer(不推荐int,因为当age参数不存在时,会返回null,而null不能转化为int,从而报错)
//日期默认支持格式:yyyy/MM/dd (2012/12/12) 自动转化为Date
//可以用String接收多选框数据,显示为 apple,banana 不过不推荐 一般用数组接收
//注意:如果格式无法转化会报400错误,如果出现400错误要注意检查在的参数是否匹配
public String login(String username, String password, Integer age, Date date, String[] hb) {
System.out.println("username = " + username);//chen
System.out.println("password = " + password);//112
System.out.println("age = " + age);//18
System.out.println("date = " + date);//Wed Dec 12 00:00:00 CST 2012
System.out.println("hb = " + Arrays.toString(hb));//[apple, banana]
return "index";
}
@RequestParam注解使用
开发中,也会碰到请求参数name的值与方法的参数名不同,这时就需要使用@RequestParam注解!
@RequestMapping("login")
//使用@RequestParam注解后,形参值就可以是任意名
//defaultValue设定默认值,当表单没有提交该数据时,会默设定该值
//多选框数据可以使用集合接收
public String login(@RequestParam(value="username")String name,
@RequestParam(value = "password",defaultValue = "1234") String pwd,
@RequestParam(value = "list")ArrayList<String> list) {
System.out.println("name = " + name);
System.out.println("pwd = " + pwd);
System.out.println("list = " + list);//list = [apple, banana]
return "index";
}
以后分页数据可以如下设置
@RequestMapping("/page")
public String queryPage(@RequestParam(defaultValue = "1")Integer currentPage,
@RequestParam(defaultValue = "10")Integer pageSize){
return "index";
}
@PathVariable获取路径参数
我们可以通过此注解,获取路径部分的数据!
@RequestMapping("path/{id}")
//{id}表示占位符 (可以是任意字符)
//@PathVariable从路径里面获取数据 http://localhost:8080/mvc/cgp/path/hello
//获取路径/path/后面的hello数据
//只会解析一层 如果是path/hello/world 则会404
public String path(@PathVariable("id") String path) {
System.out.println("path = " + path);//path = hello
return "index";
}
@CookieValue
@RequestMapping("cookie")
public String cookie(@CookieValue("JSESSIONID")String cookie){
System.out.println("cookie = " + cookie);//cookie = 25427660AB818EEFE1A27FCF71AD95BB
return "index";
}
@RequestHeader
@RequestHeader注解可以获取请求头中的数据!!
@RequestMapping("header")
public String header(@RequestHeader("User-Agent")String header){
System.out.println("header = " + header);
return "index";
}
params请求表达式
通过表达式精准映射请求
- params和headers支持简单的表达式
- param:表示请求必须包含名为param的请求参数
- !param:表示请求中不能包含名为param的参数
- param != value:表示请求中包含param的请求参数,但是值不能为value
- param == value:表示请求中包含param的请求参数,但是值为value
@RequestMapping(value = "/param" , params = {"!username","age!=10"})
public String testParam(String usernam , Integer age){
System.out.println("usernam:"+usernam);
System.out.println("age:"+age);
return "index";
}
param 和 header
@RequestMapping(value = "/param1" ,headers={"Connection!=keep-alive"},params = {"!username","age!=10"})
public String testParam1(String usernam , Integer age){
System.out.println("usernam:"+usernam);
System.out.println("age:"+age);
return "result";
}
ant风格的路径
ant风格资源地址支持3种匹配符:
- ?:匹配文件名中的一个字符
- *:匹配文件名中的任意字符
- **:匹配多层路径
代码:
@RequestMapping(value = "/?/*/xx" )
四、 其他重要操作
4.1. 转发和重定向
@Controller
public class Test {
@RequestMapping("add")
public String add() {
System.out.println("Test.add");
return "index";//转发到index.jsp 走视图解析器 可以转发到WEB-INF下的jsp
}
@RequestMapping("delete")
public String delete() {
System.out.println("Test.delete");
return "forward:add";//转发到add控制器方法 不走视图解析器
}
@RequestMapping("query")
public String query() {
System.out.println("Test.query");
return "redirect:delete";//重定向到控制器方法 不走视图解析器
}
@RequestMapping("update")
public String update() {
System.out.println("Test.update");
return "redirect:login.jsp";//重定向到jsp页面 不走视图解析器 jsp页面一定不能放在WEB-INF下
}
}
4.2. 解决参数乱码问题
Spring MVC中 GET方式不会乱码!
在web.xml配置文件中添加spring自带的Filter设置编码格式
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
五、向controller传递对象类型数据
通过form表单向指定的controller的方法传递对象!
5.1 创建POJO对象
POJO(Plain Ordinary Java Object)用来表示普通的Java对象,它不包含业务逻辑或持久逻辑等.
@Data
public class User {
private String name;
private Integer age;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date date;
private Address address;
}
public class Address {
private String province;
private String city;
public Address() {}
@Override
public String toString() {...}
getter() setter()
}
5.2 创建控制器类
@Controller
@RequestMapping("user")
public class UserController {
@RequestMapping("form")
public String form(){
return "user/form";//跳转到form表单
}
@RequestMapping("add")
public String add(User user){
System.out.println("user = " + user);
return "index";
}
}
5.3 创建form.jsp
文件位置: /WEB-INF/user/form.jsp
<form action="${pageContext.request.contextPath}/user/add" method="post">
<%--<label> 标签作用:当用户选择该标签时,浏览器就会自动将焦点转到和标签相关的表单控件上。--%>
<label for="name">用户名:<input type="text" id="name" name="name" /></label><br/>
<label for="age">年龄:<input type="text" id="age" name="age" /></label><br/>
<label for="date">生日:<input type="date" id="date" name="date" /></label><br/>
<label for="address.province">省份:<input type="text" id="address.province" name="address.province" /></label><br/>
<label for="address.city">城市:<input type="text" id="address.city" name="address.city" /></label><br/>
<input type="submit" value="提交" />
</form>
name属性要和User对象的属性相同
注意:name的特殊写法,这里可以直接将表单数据转成User对象,但是User对象内部包含 Address的对象,所以,这里可以调用第一层属性,再点一层属性,如果多层依次类推!
5.4 测试结果
user = User{name='chenganpin', age=12, date=Wed May 08 00:00:00 CST 2019, address=Address{province='浙江', city='温州'}}
六、 controller向页面传递数据
实现方案有三种: ModelAndView Map Model
往页面传递数据使用request域对象
6.1 ModelAndView
@RequestMapping("data1")
public ModelAndView request1() {
User user = new User("cgp", 18, new Date(), new Address("浙江", "温州"));
ModelAndView mav = new ModelAndView();
mav.setViewName("index");//设置视图名字 逻辑视图 走视图解析器
mav.addObject("user", user);//设置数据
return mav;
}
页面显示代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<title>结果</title>
</head>
<body>
${requestScope.user}
<%--User{name='cgp', age=18, date=Wed May 08 19:08:27 CST 2019, address=Address{province='浙江', city='温州'}}--%>
<hr/>
${requestScope.user.date}
<%--Wed May 08 19:08:27 CST 2019--%>
<hr/>
<fmt:formatDate value="${requestScope.user.date}" pattern="yyyy-MM-dd"/>
<%--2019-05-08--%>
</body>
</html>
6.2 Map
@RequestMapping("data2")
//在方法形参上写上Map<String,Object> map
public String data2(Map<String,Object> map){
User user = new User("cgp", 18, new Date(), new Address("浙江", "温州"));
map.put("user",user);//map里面存放数据 就会往request域对象放
return "index";//转发
}
页面显示代码同上
6.3 Model
将map替换成model即可!
@RequestMapping("data3")
public String data3(Model model){
User user = new User("cgp", 18, new Date(), new Address("浙江", "温州"));
model.addAttribute("user",user);
return "index";
}
总结: 使用以上三种情况可以将数据返回给页面,页面使用EL表达式即可获取!但是要注意!数据放入的是requestScope域!其他域获取不到!
验证代码:
${name}
${requestScope.name}
${sessionScope.name}
----------------------------------------
结果显示值!前两个显示!sessionScope不显示!
如果需要在sessionScope复制一份!可以利用@SessionAttributes属性!
@SessionAttributes(names = "user") //names可以选择多个值,但是必须是已有的命名!
@Controller
@RequestMapping("data")
public class DataController {...}
七、 控制器方法中使用原生API
如果我们需要使用Servlet内部常用类:
- HttpServletRequest
- HttpServletResponse
- HttpSession 等
直接在Controller层的方法中传入对应参数即可!不分顺序!
注意:如果使用maven项目 需要导入 jsp jstl servlet api
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
Java代码示例:
@Controller
@RequestMapping("servlet")
public class ServletApiController {
@RequestMapping("api")
public void test(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws ServletException, IOException {
User user = new User("cgp", 18, new Date(), new Address("浙江", "温州"));
ServletContext context = request.getServletContext();
request.setAttribute("user",user);
session.setAttribute("user",user);
context.setAttribute("user",user);
System.out.println(session.getId());
request.getRequestDispatcher("/WEB-INF/jsp/index.jsp").forward(request,response);
}
}
八、 RESTful风格编码
8.1 RESTful介绍
REST:即Representational State Transfer , (资源)表现层状态转化,是目前最流行的一种互联网软件架构。它结构清晰、符合标注、易于理解、方便扩展,所以越来越多的网站采用!
具体说,就是HTTP协议里面,四个表示操作方式的动词:
GET POST PUT DELETE
它们分别代表着四种基本操作:
- GET用来获取资源 (查)
- POST用来创建新资源 (增)
- PUT用来更新资源 (改)
- DELETE用来删除资源 (删)
如何通过路径和http动词获悉要调用的功能:
请求方式 含义
GET /zoos 列出所有动物园
POST /zoos 新建一个动物园
GET /zoos/ID 获取某个指定动物园的信息
PUT /zoos/ID 更新某个指定动物园的信息(提供该动物园的全部信息)
DELETE /zoos/ID 删除某个动物园
GET /zoos/ID/animals 列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID 删除某个指定动物园的指定动物
8.2 为什么使用RESTful
1.JSP技术可以让我们在页面中嵌入Java代码,但是这样的技术实际上限制了我们的开发效率,因为需要我们Java工程师将html转换为jsp页面,并写一些脚本代码,或者前端代码。这样会严重限制我们的开发效率,也不能让我们的java工程师专注于业务功能的开发,所以目前越来越多的互联网公司开始实行前后端分离。
2.近年随着移动互联网的发展,各种类型的Client层出不穷,RESTful可以通过一套统一的接口为Web,iOS和Android提供服务。另外对于广大平台来说,比如微博开放平台,微信开放平台等,它们不需要有显式的前端,只需要一套提供服务的接口,RESTful无疑是最好的选择。
8.3 Spring中实现RESTful风格
HiddenHttpMethodFilter:浏览器form表单只支持GET和POST,不支持DELETE和PUT请求,Spring添加了一个过滤器,可以将这些请求转换为标准的http方法,支持GET,POST,DELETE,PUT请求!
8.4 具体实现
web.xml添加HiddenHttpMethodFilter配置, 使form表单支持 put delete的过滤器
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在控制器实现增删改查操作
@Controller
@RequestMapping("rest")
public class RestController {
@RequestMapping(value = "user", method = RequestMethod.POST)
// http://localhost:91/mvc/rest/user post 新增一条用户数据
public String add(User user) {
System.out.println("user = " + user);
return "success";
}
@RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
// http://localhost:91/mvc/rest/user/1 delete 删除一条用户数据
public String delete(@PathVariable Integer id) {
System.out.println("delete-->" + id);
return "success";
}
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
// http://localhost:91/mvc/rest/user delete 删除所有用户数据
public String deleteAll() {
return "success";
}
@RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
// http://localhost:90/mvc/rest/user/1 put 更新id是1的用户
public String update(@PathVariable Integer id, User user) {
System.out.println(id + "-->" + user);
return "success";
}
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
// http://localhost:90/mvc/rest/user/1 get 根据id查询用户
public String query(@PathVariable Integer id) {
return "success";
}
@RequestMapping(value = "/user", method = RequestMethod.GET)
// http://localhost:90/mvc/rest/user get 查询所有用户
public String queryAll() {
return "success";
}
}
Jsp代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>REST表单</title>
<script type="text/javascript" src="${pageContext.request.contextPath}/static/js/jquery-1.11.0.js"></script>
<script type="text/javascript">
$(function () {
$("#btn1").click(function () {
$("#addForm").submit();//提交表单
});
$("#btn2").click(function () {
var id=1;//模拟获取表单用户id
$("#deleteForm").attr("action","${pageContext.request.contextPath}/rest/user/"+id);
$("#deleteForm").submit();//提交表单
});
$("#btn3").click(function () {
var id=1;//模拟获取表单用户id
$("#putForm").attr("action","${pageContext.request.contextPath}/rest/user/"+id);
$("#putForm").submit();//提交表单
});
})
</script>
</head>
<body>
<h3>新增用户</h3>
<form action="${pageContext.request.contextPath}/rest/user" id="addForm" method="post">
用户名:<input type="text" name="username"/><br/>
年龄:<input type="age" name="age"/><br/>
<input type="button" id="btn1" value="新增"/>
</form>
<h3>删除用户</h3>
<form action="" id="deleteForm" method="post">
<input type="hidden" name="_method" value="delete"/>
<input type="button" id="btn2" value="删除"/>
</form>
<h3>更新用户</h3>
<form action="" id="putForm" method="post">
<input type="hidden" name="_method" value="put"/>
用户名:<input type="text" name="username"/><br/>
年龄:<input type="age" name="age"/><br/>
<input type="button" id="btn3" value="更新"/>
</form>
</body>
</html>
需要注意: 由于doFilterInternal方法只对method为post的表单进行过滤,所以在页面中必须如下设置:
<form action="..." method="post">
<input type="hidden" name="_method" value="put" />
</form>
代表post请求,但是HiddenHttpMethodFilter将把本次请求转化成标准的put请求方式! name="_method"固定写法!
九、 处理静态资源
需要注意一种,DispatcherServlet拦截资源设置成了 / 避免了死循环,但是 / 不拦截jsp资源,但是它会拦截其他静态资源,例如 html , js , 图片等等, 那么我们在使用jsp内部添加 静态资源就无法成功,所以,我们需要单独处理下静态资源!
修改Spring MVC对应配置文件,添加mvc命名空间和约束
<!--配置springmvc拦截静态资源
location:要放行的路径
mapping:映射的路径 -->
<mvc:resources mapping="/static/**" location="/static/"/>
<!--放行webapp下面的所有静态资源 但不能放行WEB-INF资源下的静态资源-->
<mvc:default-servlet-handler/>
配置解释: 将静态资源(图片,css,js,html)放入了webApp/static文件夹下! 直接访问DispatcherServlet会拦截出现404问题!
location元素表示webapp目录下的static包下的所有文件;
mapping元素表示将 location对应文件加下的资源对应到 /static路径下!
该配置的作用是:DispatcherServlet不会拦截以/static开头的所有请求路径,并当作静态资源交由Servlet处理
十、返回JSON数据
访问控制器返回Json类型数据!
导入对应的JSON包! Spring-MVC 推荐导入Jackson包
主要使用@ResponseBody
@Controller
@RequestMapping("json")
public class JsonController {
//1.使用servlet原生api方式实现json
@RequestMapping("json1")
public void getJson(HttpServletResponse response) throws IOException {
User user = new User("cgp", 18, new Date(), new Address("浙江", "温州"));
response.setContentType("text/json;charset=utf-8");
String s = JsonUtils.objectToJson(user);
response.getWriter().append(s);
}
//2.使用spring-mvc自带的方式
@RequestMapping("json2")
@ResponseBody //需要在方法头添加ResponseBody
public User getJson2() {
User user = new User("cgp", 18, new Date(), new Address("浙江", "温州"));
return user;
}
//3.json存放list集合
@RequestMapping("json3")
//可以把ResponseBody加在方法体中
public @ResponseBody Object getJson3() {
List<User> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
User user = new User("cgp", 18, new Date(), new Address("浙江", "温州"));
list.add(user);
}
return list;
}
//4.json存放map集合
@RequestMapping("json4")
@ResponseBody
public Object getJosn4() {
Map<String, Object> map = new HashMap<>();
for (int i = 0; i < 3; i++) {
User user = new User("cgp", 18, new Date(), new Address("浙江", "温州"));
map.put("0" + i, user);
}
return map;
}
//5.json存放字符串
//produces:设置响应体的数据格式和编码
@RequestMapping(value = "json5",produces = "text/plain;charset=utf-8")
@ResponseBody
public Object getJson5(){
return "我是字符串";
}
}
回顾Spring MVC返回值类型
-
String
-
情况1: 查找到指定的视图
return “user/show”;
-
情况2: 转发或者重定向
return “redirect: path”;
return “forword:path”;
-
-
ModelAndView
返回数据视图模型对象!
ModelAndView mv = new ModelAndView();
mv.setViewName(“查找视图路径”);
mv.addObject(“key”,“object type data”);
-
Object
配合@ResponseBody返回Json数据类型!
-
void
可以返回其他mimetype类型的数据!通常将方法定义成void
配合方法传参得到Response对象,通过Response.getWriter().writer(“数据”);
十一、Spring MVC异常处理
11.1 Spring MVC异常处理介绍
Spring MVC通过HandlerExceptionResolver处理程序的异常,包括处理映射,数据绑定及处理器执行时发生异常。HandlerExceptionResolver仅有一个接口方法:
ModelAndView resolveException(HttpServletRequest reqeust,HttpServletResponse response,Object handler,Exception ex);
当发生异常时,Spring MVC将调用 resolveException()方法,并转到ModelAndView对应视图中,作为一个异常报告页面,反馈给用户!
HandlerExceptionResolver拥有4个实现类:
- DefaultHandlerExceptionResolver
- SimpleMappingExceptionResolver
- AnnotationMethodHandlerExceptionResolver
- ResponseStatusExceptionResolver
11.2 异常处理方案
11.2.1 DefaultHandlerExceptionResolver
Spring MVC默认装配了DefaultHandlerExceptionResolver,它会将Spring MVC框架的异常转换为相应的相应状态码!
异常和相应状态码对应表
异常类型 | 响应状态码 |
---|---|
ConversionNotSupportedException | 500(Web服务器内部错误) |
HttpMediaTypeNotAcceptableException | 406(无和请求accept匹配的MIME类型) |
HttpMediaTypeNotSupportedException | 415(不支持MIME类型) |
HttpMessageNotReadableException | 400 |
HttpMessageNotWritableException | 500 |
HttpRequestMethodNotSupportedException | 405 |
MissingServletRequestParameterException | 400 |
在web.xml响应状态码配置一个对应页面
<error-page>
<error-code>404</error-code>
<location>/404.html</location>
</error-page>
注意: 静态资源注意会被DispatcherServlet拦截!
11.2.2 AnnotationMethodHandlerExceptionResolver
Spring MVC 默认注册了 AnnotationMethodHandlerExceptionResolver,它允许通过@ExceptionHandler注解指定处理特定异常的方法!
@ExceptionHandler
public String handleException(RuntimeException re, HttpServletRequest request)
{
return "forward:/user/error";
}
通过@ExceptionHandler指定了当前类的一个错误处理方法!如果当前类中出现异常,会触发错误处理方法!
但是@ExceptionHandler的异常处理方法只能对同一处理类中的其他处理方法进行异常响应处理!!
11.2.3 全局异常处理
@ControllerAdvice
public class MyHandlerException {
//log4j
private Logger logger = Logger.getLogger(MyHandlerException.class);
//出现Exception类型异常时,就会执行该方法
@ExceptionHandler(Exception.class)
public ModelAndView doException(Exception e) {
logger.error(e.getMessage());//记录日志文件
e.printStackTrace();//控制台直接打印
//指定页面跳转
ModelAndView mav = new ModelAndView();
mav.setViewName("error");
mav.addObject("msg", e.getMessage());
return mav;
}
}
此处可以捕捉全局异常,但是不要忘了在spring配置的时候扫描该类!
十二、 Spring MVC拦截器实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-adpWdb4d-1575040207829)(images/springmvc拦截器.png)]
Spring MVC 的拦截器类似于Servlet中的过滤器Filter!需要先定义一个类实现HandlerInterceptor接口!
添加未实现的方法,在springmvc配置中配置!具体实现步骤如下:
拦截器是SpringMVC提供的 不基于web服务器运行,过滤器是基于web服务器的。过滤可以过滤所有请求,
拦截器不拦截jsp文件的。过滤器在web.xml配置。拦截器在springMVC的配置文件里面配置。
12.1 创建拦截器类
public class MyInteceptor1 implements HandlerInterceptor {
/**
* 在调用目标处理器之前执行过滤请求:可以对请求进行拦截或者放行
* @param request 请求头
* @param response 响应对象
* @param handler 目标handler
* @return true 表示放行 false表示拦截
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInteceptor1.preHandle"+handler.toString());
return true;
}
/**
* 走完目标handler之后会走该方法
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInteceptor1.postHandle");
}
/**
* 页面渲染后会触发该方法
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInteceptor1.afterCompletion");
}
}
- boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler):在请求到达Handler之前,先执行这个前置处理方法.当该方法返回false时,请求直接返回,不会传递到链中的下一个拦截器,更不会传递到链尾的Handler,只有返回true时,请求才会向链中的下一个处理节点传递!
- void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView); 在相应已经被渲染后,执行该方法.
- void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); 在响应已经被渲染后,执行该方法!
12.2 在SpringMVC 配置中拦截器
<mvc:interceptors>
<!--配置要拦截的路径-->
<mvc:interceptor>
<!-- /** 代表拦截所有路径-->
<mvc:mapping path="/**"/>
<!--配置不拦截哪些路径-->
<mvc:exclude-mapping path="/user/**/"/>
<!--配置拦截器的bean-->
<bean class="springmvc.inteceptors.MyInteceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
十三、 Spring MVC处理文件上传
Spring MVC为文件上传提供了直接支持,这种支持是通过即插即用的MultipartResolver实现. Spring使用Jakarta Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver。
在SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要先在上下文中配置MultipartResolver。
引入jar包
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
配置文件上传MultipartResolver
<!--配置文件上传-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--设置文件上传的总大小 4Mb-->
<property name="maxUploadSize" value="#{1024*1024*4}"/>
<!--设置单个文件的最大值-->
<property name="maxUploadSizePerFile" value="#{1024*1024}"/>
<!--设置编码-->
<property name="defaultEncoding" value="UTF-8"/>
<!--指定一个临时上传的文件夹目录-->
<property name="uploadTempDir" value="file:D:\temp"/>
</bean>
编写控制器和文件上传表单
- 编写文件上传表单 upload.jsp
<form action="${pageContext.request.contextPath}/upload/save" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>
<input type="submit" value="上传"/>
</form>
- 编写控制器代码
@RequestMapping("save")
@ResponseBody //接收表单数据
//MultipartFile 表示接收文件类型的表单数据
public String upload(MultipartFile file, HttpServletRequest request) throws IOException {
System.out.println("上传的文件名:"+file.getOriginalFilename());//mvc.png
System.out.println("MIME:"+file.getContentType());//image/png
//文件上传
String realPath = request.getServletContext().getRealPath("/upload");
System.out.println(realPath);
//H:\JavaProject\springmvc\src\main\webapp\upload
File dir = new File(realPath);
if (!dir.isDirectory()) {//如果不是文件夹
dir.delete();//删除这个文件
dir.mkdirs();//创建文件夹
}
//设置上传文件的名字
String fileName = UUID.randomUUID().toString().replace("-", "") + file.getOriginalFilename();
//设置文件存放路径
File dest = new File(realPath + "/" + fileName);
//上传文件
file.transferTo(dest);
return "ok";
}
多文件上传
<form action="${pageContext.request.contextPath}/upload/save2" enctype="multipart/form-data" method="post">
<input type="file" name="file"/>
<hr/>
<input type="file" name="file"/>
<hr/>
<input type="submit" value="上传"/>
</form>
@RequestMapping("save2")
@ResponseBody
public Map<String,String> upload2(MultipartFile[] file,HttpServletRequest request) throws IOException {
String realPath = request.getServletContext().getRealPath("/upload");
File dir = new File(realPath);
if (!dir.isDirectory()){
dir.delete();
dir.mkdirs();
}
HashMap<String, String> map = new HashMap<>();
int i=0;
for (MultipartFile f : file) {
String fileName = UUID.randomUUID().toString().replace("-", "") + f.getOriginalFilename();
File dest = new File(realPath + "/" + fileName);
f.transferTo(dest);
map.put(f.getOriginalFilename()+":"+(++i),realPath+"/"+fileName);
}
return map;//向页面传输json数组
}
文件下载
<body>
<a href="${pageContext.request.contextPath}/down">下载资源</a>
</body>
@RequestMapping("down")
public void down(HttpServletRequest request, HttpServletResponse response) throws IOException {
String realPath = request.getServletContext().getRealPath("upload/f18db1f0b972437790292c7c024ad0f4mvc-context-hierarchy.png");
File dest = new File(realPath);//要下载的目标资源
//告诉浏览器以下载的形式打开该文件
response.setHeader("Content-Disposition","attachment;filename="+dest.getName());
response.setContentType("application/octet-stream;charset=UTF-8");
//构建文件的输入流对象
FileInputStream fis = new FileInputStream(dest);
byte[] buf = new byte[fis.available()];//fis.available() fis中的文件的字节数
fis.read(buf);//读到数组
response.getOutputStream().write(buf);
}
SpringMVC会将上传文件绑定到MultipartFile对象上. MultipartFile提供了获取长传文件内容,文件名等方法,通过transferTo()方法还可将文件存储到磁盘中,具体方法如下:
方法名称 | 方法解释 |
---|---|
byte [] getBytes() | 获取文件数据 |
String getContentType() | 获取文件MIMETYPE类型,如image/jpeg,text/plain等 |
InputStream getInputStream() | 获取文件输入流 |
String getName() | 获取表单中文件组件的名称 name值! |
String getOriginalFilename() | 获取文件上传的原名 |
long getSize() | 获取文件的字节大小,单位为byte |
boolean isEmpty() | 是否有长传的文件 |
void transferTo(File dest) | 可以将上传的文件保存到指定的文件中 |
Mybatis
一、MyBatis介绍
1.1 介绍
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml方式将要执行的各种statement(statement、preparedStatement、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
Mybatis是一个orm框架,对jdbc复杂的操作做封装,然后把sql语句抽取到配置文件中。
1.2 jdbc问题总结
-
数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
-
Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
-
使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
-
对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。
1.3 Mybatis架构
-
mybatis配置
SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。
-
通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂
-
由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。
-
mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
-
MappedStatement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个MappedStatement对象,sql的id即是Mappedstatement的id。
-
MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。
-
MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
二、Mybatis入门程序
maven依赖mybatis mysql-connector-java 相关日志文件
配置SqlMapConfig.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<!--单个别名定义-->
<typeAlias type="cgp.pojo.User" alias="ur"/>
<!--批量别名定义,扫描整个包下的类,别名为类名(字母大小写都行)-->
<package name="cgp.pojo"/>
</typeAliases>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理-->
<transactionManager type="JDBC"/>
<!-- 数据库连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb?serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="1111"/>
</dataSource>
</environment>
</environments>
<!-- 引入sql映射文件 -->
<mappers>
<mapper resource="cgp/mapper/UserMapper.xml"/>
<mapper resource="cgp/mapper/UserMapper2.xml"/>
</mappers>
</configuration>
编写pojo类
public class User {
private Integer uid;
private String uname;
private String upwd;
private Date birthday;
private Integer age;
getter() setter()
@Override
public String toString() {...}
public User() {
}
}
编写映射xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:理解成实现类的全路径(包名+类名)用来隔离sql语句-->
<mapper namespace="cgp.pojo.User">
<!-- id:方法名 唯一。
#{占位符的名字}:当输入参数只有1个 并且是基础类型的时候(8种基本类型和String),变量名的值可以任意起。
parameterMap;指设置输入参数的类型
resultType:设置输出参数的类型
-->
<!--根据id查询-->
<select id="selById" parameterType="int" resultType="cgp.pojo.User">/*原始方式*/
select * from user where uid=#{uid}
</select>
<!--查询所有-->
<select id="selAll" resultType="ur">/*使用单个别名*/
select * from user
</select>
<!--聚合函数-->
<select id="selCount" resultType="int">
select count(uid) from user
</select>
<!--删除-->
<delete id="delById" parameterType="int">
delete from user where uid=#{uid}
</delete>
<!--修改-->
<update id="updById" parameterType="User">/*使用批量别名定义*/
update user set uname=#{uname},upwd=#{upwd} where uid=#{uid}
</update>
<!--新增-->
<insert id="insUser" parameterType="ur">
insert into user(uname,upwd,age,birthday) values(#{uname},#{upwd},#{age},#{birthday})
</insert>
<!--获取新增的自增主键值 方式一-->
<insert id="insUser2" parameterType="ur">
/*
keyColumn 表的字段
keyProperty 表字段对应的属性
resultType 字段对应的类型
order AFTER表示在insert语句之后执行,就能获取当前插入语句的值
*/
<selectKey keyColumn="uid" keyProperty="uid" resultType="int" order="AFTER">
select last_insert_id()/*该语句要跟插入语句同时使用*/
</selectKey>
insert into user(uname,upwd,age) values(#{uname},#{upwd},#{age})
</insert>
<!--获取新增的自增主键值 方式二 推荐使用-->
<insert id="insUser3" useGeneratedKeys="true" keyColumn="uid" keyProperty="uid" parameterType="ur">
insert into user(uname,upwd,age) values (#{uname},#{upwd},#{age})
</insert>
<!--主键是uuid字符串-->
<insert id="insUser4" parameterType="User1">
<selectKey keyProperty="uid" keyColumn="uid" order="BEFORE" resultType="java.lang.String">
select replace(uuid(),'-','')
</selectKey>
insert into user2(uid,uname,upwd) values(#{uid},#{uname},#{upwd});
</insert>
<!--获取主键是字符串的新插入的用户的主键值--> 未测试成功
<insert id="insUser5" useGeneratedKeys="true" keyColumn="uid" keyProperty="uid" parameterType="User1">
insert into user2 values(#{uid},#{uname},#{upwd})
</insert>
</mapper>
测试类
public class DemoUser {
public static void main(String[] args) throws IOException {
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//加载配置文件构建SqlSessionFactory 类似于连接池
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
//获取SqlSession 会自动开启事务
SqlSession sqlSession = sqlSessionFactory.openSession();
/**
* selectOne:结果集只能有0或者1 0返回null多个会报错。
* selectList:结果集为多个
* selectMap:查找某个字段方便
*/
User u = sqlSession.selectOne("selById", 11);
System.out.println(u);
List<Object> selAll = sqlSession.selectList("selAll");
System.out.println("selAll = " + selAll);
Object selCount = sqlSession.selectOne("selCount");
System.out.println("selCount = " + selCount);
Map<Object, Object> map = sqlSession.selectMap("cgp.pojo.User.selAll", "uname");
System.out.println("map = " + map);
//实现删除
int row = sqlSession.delete("cgp.pojo.User.delById",20);
System.out.println("row = " + row);
User user=new User();
user.setUname("chen");
user.setUpwd("12345");
user.setAge(27);
user.setBirthday(new Date());
User1 user1=new User1();
user1.setUname("chen");
user1.setUpwd("111");
//实现修改
user.setUid(11);
int row = sqlSession.update("updById", user);
System.out.println("row = " + row);
//实现新增
user.setBirthday(new Date());
int row = sqlSession.insert("insUser",user);
System.out.println("row = " + row);
//获取刚插入的数据的主键id值
int row = sqlSession.insert("insUser2", user);//方式一
int row = sqlSession.insert("insUser3", user);//方式二 推荐
int row = sqlSession.insert("insUser4", user1);//主键是uuid字符串
int row = sqlSession.insert("insUser5", user1);//主键是字符串 自增
System.out.println("row = " + row);
System.out.println(user.getUid());//获取主键id值
// sqlSession.rollback(); //回滚
sqlSession.commit();//提交事务
}
}
三、Mapper动态代理方式
Mapper接口开发需要遵循以下规范:
1、 Mapper.xml文件中的namespace与mapper接口的类路径相同。(一般情况下 mapper接口和sql映射文件名字一致,并且放在同一目录下。)
2、 Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
3、 Mapper接口方法的 形参 和mapper.xml中定义的每个sql 的parameterType的类型一致。
4、 Mapper接口方法的返回值 和mapper.xml中定义的每个sql的resultType的类型一致
3.1 配置映射文件Mapper.xml
定义mapper映射文件UserMapper.xml(内容同Users.xml),需要修改namespace的值为 UserMapper接口路径。将UserMapper.xml放在classpath 下mapper目录 下。
<!--namespace:固定是以下方式-->
<mapper namespace="cgp.mapper.UserMapper">
<!--根据id查询-->
<select id="selById" parameterType="int" resultType="cgp.pojo.User">/*原始方式*/
select * from user where uid=#{uid}
</select>
<!--查询所有-->
<select id="selAll" resultType="ur">/*使用单个别名*/
select * from user
</select>
<!--聚合函数-->
<select id="selCount" resultType="int">
select count(uid) from user
</select>
<!--删除-->
<delete id="delById" parameterType="int">
delete from user where uid=#{uid}
</delete>
<!--修改-->
<update id="updById" parameterType="User">/*使用批量别名定义*/
update user set uname=#{uname},upwd=#{upwd} where uid=#{uid}
</update>
<!--新增-->
<insert id="insUser" parameterType="ur">
insert into user(uname,upwd,age,birthday) values(#{uname},#{upwd},#{age},#{birthday})
</insert>
<!--分页1-->
<select id="selByPage" parameterType="map" resultType="user">
select * from user limit #{begin},#{limit}
</select>
<!--分页2-->
<select id="selByPage2" parameterType="expanduser" resultType="user">
select * from user limit #{begin},#{limit}
</select>
<!--分页3-->
<select id="selByPage3" resultType="user" parameterType="expanduser">
select * from user limit #{param1},#{param2}
</select>
<!--分页4-->
<select id="selByPage4" resultType="user">
select * from user limit #{begin},#{limit}
</select>
</mapper>
3.3 Mapper.java(接口文件)
public interface UserMapper {
User selById(Integer uid);
List<User> selAll();
int selCount();
int delById(Integer uid);
int updById(User user);
List<User> selByPage(Map<String,Integer> map);
List<User> selByPage2(ExpandUser expandUser);
List<User> selByPage3(Integer begin, Integer limit);
List<User> selByPage4(@Param("begin") Integer begin, @Param("limit") Integer limit);
}
3.4 加载UserMapper.xml文件
修改SqlMapConfig.xml文件:
<mappers>
<!--注册指定包下的所有mapper接口
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
-->
<package name="cgp.mapper"/>
</mappers>
3.5 测试类
public class DemoMapper {
SqlSession session=null;
@Before//表示执行该类里面的任意@Test方法之前都会去执行该代码
public void init() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(is);
session = build.openSession();
}
@After
public void des(){
session.commit();
session.close();
}
@Test
public void run(){
//获取框架给我们动态代理方式创建的mapper接口的实现类
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selById(1);
System.out.println("user = " + user);
}
@Test
public void run2(){
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> list = mapper.selAll();
System.out.println("list = " + list);
}
@Test
public void run3(){
UserMapper mapper = session.getMapper(UserMapper.class);
int row = mapper.selCount();
System.out.println("row = " + row);
}
@Test
public void run4(){
UserMapper mapper = session.getMapper(UserMapper.class);
int row = mapper.delById(30);
System.out.println("row = " + row);
}
@Test
public void run5(){
UserMapper mapper = session.getMapper(UserMapper.class);
User user=mapper.selById(29);
user.setUname("manaphy");
int row = mapper.updById(user);
System.out.println("row = " + row);
}
@Test
public void run6(){
UserMapper mapper = session.getMapper(UserMapper.class);
Map<String,Integer> map=new HashMap<>();
map.put("begin", 0);
map.put("limit", 5);
List<User> users = mapper.selByPage(map);
System.out.println(users);
}
@Test
public void run7(){
UserMapper mapper = session.getMapper(UserMapper.class);
ExpandUser expandUser=new ExpandUser();
expandUser.setBegin(0);
expandUser.setLimit(5);
List<User> users = mapper.selByPage2(expandUser);
System.out.println(users);
}
@Test
public void run8(){
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.selByPage3(0,5);
System.out.println(users);
}
@Test
public void run9(){
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.selByPage4(0,5);
System.out.println(users);
}
}
四、输入映射和输出映射
4.1 pojo包装对象作为输入映射
ExpandUser作为输入映射
public class ExpandUser {
private User user;
private Integer begin;
private Integer limit;
@Override
public String toString() {...}
getter和setter
public ExpandUser() {}
}
<select id="selPages" parameterType="expanduser" resultType="user">
select * from user where uname like concat("%",#{user.uname},"%") limit #{begin},#{limit};
</select>
写接口方法
List<User> selPages(ExpandUser expandUser);
测试类
@Test
public void run(){
UserMapper2 mapper = session.getMapper(UserMapper2.class);
ExpandUser user = new ExpandUser();
User user1 = new User();
user1.setUname("chen");
user.setUser(user1);
user.setBegin(0);
user.setLimit(3);
List<User> users = mapper.selPages(user);
System.out.println("users = " + users);
}
[cgp.mapper.UserMapper2.selPages]==> Preparing: select * from user where uname like concat("%",?,"%") limit ?,?;
[cgp.mapper.UserMapper2.selPages]==> Parameters: chen(String), 0(Integer), 3(Integer)
[cgp.mapper.UserMapper2.selPages]<== Total: 3
4.2 解决属性名和字段名不一致
<!--起别名来解决属性名和字段名不一致-->
<select id="selAll" resultType="user2">
select uid,uname username,upwd,age from user
</select>
<!--定义一个resultMap:数据库表的字段和java类的属性进行一一映射-->
<resultMap id="User2Map" type="User2">
<id property="uid" column="uid"></id>
<result property="username" column="uname"/>
<result property="upwd" column="upwd"/>
<result property="age" column="age"/>
</resultMap>
<!--使用resultMap解决属性名与字段名不一致-->
<select id="selAll2" resultMap="User2Map">
select * from user
</select>
4.3 动态sql
通过mybatis提供的各种标签方法实现动态拼接sql。
<!--动态sql之if标签-->
<!--根据user对象里面的age是否为空或者空串来动态的拼接sql语句-->
<select id="selByIf" parameterType="user" resultMap="UserMap">
select * from user where 1=1
/*if表示判断,有点类似于c:if标签*/
<if test="age!=null and age!=''">
and age=#{age}
</if>
<if test="uname!=null and uname!=''">
and uname like concat("%",#{uname},"%")
</if>
</select>
<!--动态sql之where标签-->
<select id="selByWhere" parameterType="user" resultMap="UserMap">
select * from user
/*where标签表示条件,就表示where关键字,但是功能要比where关键字强大
能够帮助我们去掉where标签体里面包裹着的第一个and或者or*/
<where>
<if test="age!=null and age!=''">
and age=#{age}
</if>
<if test="uname!=null and uname!=''">
and uname like concat("%",#{uname},"%")
</if>
</where>
</select>
<!--动态sql之choose-when—otherwise标签-->
<!--如果传入了某个参数值,那就只查询这个字段,如果没传入,就看下一个字段是否传入,
如果这些字段值都没有传入,那就按默认的条件查询-->
<select id="selWhenOhterwise" parameterType="user" resultMap="UserMap">
select * from user
<where>
/*when可以有多个 otherwise只能有一个 条件只会走一个*/
<choose>
<when test="uid!=null">uid=#{uid}</when>
<when test="uname!=null and uname!=''">uname like concat("%",#{uname},"%")</when>
<when test="age!=null and age!=''">age=#{age}</when>
<otherwise>uid=1</otherwise>
</choose>
</where>
</select>
<!--动态SQL之Set标签-->
<update id="updSet" parameterType="user">
update user
/*set标签用来做更新的,set标签相当于set关键字 且可以帮助我们去掉最后一个逗号,*/
<set>
<if test="uname!=null and uname!=''">
uname=#{uname},
</if>
<if test="age!=null and age!=''">
age=#{age},
</if>
</set>
where uid=#{uid}
</update>
<!--动态sql之trim-where标签 -->
<!--代替where:帮助我们去掉第一个and或者or
prefix:给trim所包裹的语句添加某些前缀
suffix:给trim所包裹的语句添加某些后缀
suffixOverrides:给trim所包裹的语句去掉某些后缀
prefixOverrides:给trim所包裹的语句去掉某些前缀-->
<select id="selTrimWhere" parameterType="user" resultMap="UserMap">
select * from user
<trim prefix="where" prefixOverrides="and|or">
<if test="uname!=null and uname!=''">
and uname like concat("%",#{uname},"%")
</if>
<if test="age!=null and age!=''">
and age=#{age}
</if>
</trim>
</select>
<!--动态sql之trim-set标签-->
<!--代替set:set关键字+去掉最后一个
prefix:给trim所包裹的语句添加某些前缀
suffix:给trim所包裹的语句添加某些后缀
suffixOverrides:给trim所包裹的语句去掉某些后缀
prefixOverrides:给trim所包裹的语句去掉某些前缀-->
<update id="updTrimSet" parameterType="user">
update user
<trim prefix="set" suffixOverrides="," suffix="where uid=#{uid}">
<if test="uname!=null and uname!=''">
uname=#{uname},
</if>
<if test="age!=null and age!=''">
age=#{age},
</if>
</trim>
</update>
<sql id="Select_Column_List">select * from user</sql>
<!--动态sql之foreach标签-->
<!--select * from user where uid in(1,2,3,4,5);
collection:要遍历的集合
item;当前正在遍历的元素的别名
separator元素之间的分隔离
close:结束
open:开始-->
<select id="selForeach" resultMap="UserMap"> /*批量查询*/
<include refid="Select_Column_List"/>/*引用标签id的内容*/
where uid in
<foreach collection="ids" item="uid" separator="," close=")" open="(">
#{uid}
</foreach>
</select>
<delete id="delBatch">/*批量删除*/
delete from user where uid in
<foreach collection="ids" open="(" close=")" separator="," item="uid">
#{uid}
</foreach>
</delete>
<insert id="insBatch">/*批量新增*/
insert into user (uname,upwd)
<foreach collection="users" item="user" open="values" separator=",">
(#{user.uname},#{user.upwd})
</foreach>
</insert>
最后三个接口案例
List<User> selForeach(@Param("ids")int[]arr);
int delBatch(@Param("ids") List<Integer> ids);
int insBatch(@Param("users") List<User> list);
最后两个案例测试类
@Test
public void run10(){//遍历集合
UserMapper2 mapper = session.getMapper(UserMapper2.class);
int[] arr={1,2,3,4,5};
List<User> users = mapper.selForeach(arr);
System.out.println("users = " + users);
}
@Test
public void run11(){//遍历集合
UserMapper2 mapper = session.getMapper(UserMapper2.class);
int row = mapper.delBatch(Arrays.asList(16, 19));
System.out.println("row = " + row);
}
@Test
public void run12() {//遍历集合
UserMapper2 mapper = session.getMapper(UserMapper2.class);
List<User> list=new ArrayList<>();
for (int i = 0; i <100; i++) {
User user=new User();
user.setUname("chen-"+i);
user.setUpwd(""+i);
list.add(user);
}
int row = mapper.insBatch(list);
System.out.println("row = " + row);
}
五、关联映射
在现实的项目中进行数据库建模时,我们要遵循数据库设计范式的要求,会对现实中的业务模型进行拆分,封装在不同的数据表中,表与表之间存在着一对多或是多对多的对应关系。进而,我们对数据库的增删改查操作的主体,也就从单表变成了多表。那么Mybatis中是如何实现这种多表关系的映射呢?
查询结果集ResultMap
resultMap 元素是 MyBatis 中最重要最强大的元素。它就是让你远离 90%的需要从结果 集中取出数据的 JDBC 代码的那个东西,而且在一些情形下允许你做一些 JDBC 不支持的事 情。 事实上, 编写相似于对复杂语句联合映射这些等同的代码,也许可以跨过上千行的代码。
有朋友会问,之前的示例中我们没有用到结果集,不是也可以正确地将数据表中的数据映射到Java对象的属性中吗?是的。这正是resultMap元素设计的初衷,就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。
- resultMap元素中,允许有以下直接子元素:
- constructor - 类在实例化时,用来注入结果到构造方法中(本文中暂不讲解)
- id - 作用与result相同,同时可以标识出用这个字段值可以区分其他对象实例。可以理解为数据表中的主键,可以定位数据表中唯一一笔记录
- result - 将数据表中的字段注入到Java对象属性中
- association - 关联,简单的讲,就是“有一个”关系,如“用户”有一个“帐号” 关联查询对象:一对一 多对一
- collection - 集合,顾名思议,就是“有很多”关系,如“客户”有很多“订单” 关联查询集合 :一对多 多对多
- discriminator - 使用结果集决定使用哪个个结果映射(暂不涉及)
<mapper namespace="associationmapping.mapper.UserMapper">
<!--声明一个user的resultMap-->
<resultMap id="userMap" type="user">
<id column="uid" property="uid"/>
<result property="uname" column="uname"/>
<result property="upwd" column="upwd"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
<result property="money" column="money"/>
</resultMap>
<!--声明一个orders的resultMap-->
<resultMap id="orderMap" type="orders">
<id column="oid" property="oid"/>
<result property="oname" column="oname"/>
<result property="price" column="price"/>
<result property="uid" column="uid"/>
</resultMap>
<!--使用嵌套结果映射来处理重复的联合结果的子集。-->
<select id="selOrdersWithUser" resultMap="OrderAndUserMap">
select * from orders o left join user u on u.uid=o.uid
</select>
<!--继承orderMap 可以继承它的数据库表和属性的映射关系-->
<resultMap id="OrderAndUserMap" type="OrdersExpand" extends="orderMap">
<!--配置对象属性 private User user;
property:private User user的属性名
javaType:属性名的类型 支持别名
resultMap:user对应的resultMap -->
<association property="user" javaType="user" resultMap="userMap"/>
</resultMap>
<!--嵌套查询映射 查询订单的时候去关联查询用户信息-->
<!--查询1张表 嵌套着另外一个查询-->
<select id="selOrdersWithUser2" resultMap="OrderAndUserMap2">
select * from orders
</select>
<resultMap id="OrderAndUserMap2" type="OrdersExpand" extends="orderMap">
<!--select:写另外一个sql语句的id-->
<association property="user" column="uid" javaType="user" select="selUserByUid"/>
</resultMap>
<select id="selUserByUid" resultMap="userMap">
select * from user where uid=#{uid}
</select>
<select id="selUserWithOrder" resultType="OrdersUser">
select * from orders o left join user u on u.uid=o.uid
</select>
</mapper>
<!--嵌套结果集映射:查询2张表-->
<!--查询用户的时候去关联查询用户的订单数据-->
<select id="selUserWithOrders" resultMap="UsersOrdersMap1">
select * from user u left join orders o on o.uid=u.uid
</select>
<resultMap id="UsersOrdersMap1" type="UserExpand" extends="userMap">
<!--collection:配置关联集合
private List<Orders> list;
property:配置集合对象的属性名list
ofType:指集合中的泛型的类型 支持别名
resultMap:Order的映射
-->
<collection property="list" ofType="Orders" resultMap="orderMap"/>
</resultMap>
<!--嵌套查询映射:查询1张表-->
<!--查询用户的时候去关联查询订单集合-->
<select id="selUserWithOrders2" resultMap="UsersOrdersMap2">
select * from user;
</select>
<resultMap id="UsersOrdersMap2" type="UserExpand" extends="userMap">
<collection property="list" column="uid" ofType="Orders" select="selOrdersByUid"/>
</resultMap>
<select id="selOrdersByUid" resultMap="orderMap">
select * from orders where uid=#{uid}
</select>
接口 略
测试类
public class Demo {
SqlSession session = null;
@Before
public void init() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig2.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(is);
session = build.openSession();
}
@After
public void des() {
session.commit();
session.close();
}
@Test
public void run() {
UserMapper mapper = session.getMapper(UserMapper.class);
List<OrdersExpand> list = mapper.selOrdersWithUser();
System.out.println("list = " + list);
//list = [OrdersExpand{Orders{oid=1, oname='枕头', price=200.0, uid=1}user=User{uid=1, uname='jack',
// upwd='111111', sex='男', birthday=Tue May 14 00:00:00 CST 2019, money=3000.0}},
// OrdersExpand{Orders{oid=2, oname='毛巾', price=150.0, uid=1}user=User{uid=1, uname='jack',
// upwd='111111', sex='男', birthday=Tue May 14 00:00:00 CST 2019, money=3000.0}},
// OrdersExpand{Orders{oid=3, oname='洗头膏', price=500.0, uid=2}user=User{uid=2, uname='rose',
// upwd='222222', sex='女', birthday=Wed May 15 00:00:00 CST 2019, money=2500.0}}]
}
@Test
public void run1() {
UserMapper mapper = session.getMapper(UserMapper.class);
List<OrdersExpand> list = mapper.selOrdersWithUser2();
System.out.println("list = " + list);//结果等同于上面
}
@Test
public void run2() {
UserMapper mapper = session.getMapper(UserMapper.class);
List<OrdersUser> list = mapper.selUserWithOrder();
System.out.println("list = " + list);//结果等同于上面
}
@Test
public void run3() {
UserMapper mapper = session.getMapper(UserMapper.class);
List<UserExpand> users = mapper.selUserWithOrders();
for (UserExpand user : users) {
System.out.println("user = " + user);
}
}
@Test
public void run4() {
UserMapper mapper = session.getMapper(UserMapper.class);
List<UserExpand> users = mapper.selUserWithOrders2();
for (UserExpand user : users) {
System.out.println(user);
}
}
}
SSM框架整合
整合的准备工作
web.xml
log4j.properties
db.properties
spring-mvc.xml
applicationContext-service.xml
applicationContext-tx.xml
applicationContext-dao.xml
SqlMapConfig.xml(可不配置)
Sql映射文件
pom文件:ssm 其他的工具
1.Spring整合SpringMVC
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="com.ssm.controller"/>--修改此处
<!--mvc注解驱动-->
<mvc:annotation-driven/>
<!--过滤静态资源-->
<mvc:default-servlet-handler/>
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--拦截器-->
<!--文件上传下载-->
</beans>
2.Spring整合Mybatis (dao层)
<?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: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 https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="com.ssm.mapper"/>--修改此处
<context:property-placeholder location="classpath:db.properties"/>
<!--配置数据源 druid-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="20"/>
<property name="minIdle" value="10"/>
<property name="maxActive" value="200"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="10000"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="testWhileIdle" value="true"/>
<!-- 验证连接有效与否的SQL,不同的数据配置不同 -->
<property name="validationQuery" value="select 1 "/>
<!-- 这里建议配置为TRUE,防止取到的连接不可用 -->
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="false"/>
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="false"/>
<property name="maxPoolPreparedStatementPerConnectionSize"
value="20"/>
<!-- 这里配置提交方式,默认就是TRUE,可以不用配置 -->
<property name="defaultAutoCommit" value="true"/>
<property name="filters" value="stat"/>
<property name="proxyFilters">
<list>
<ref bean="logFilter"/>
</list>
</property>
</bean>
<bean id="logFilter" class="com.alibaba.druid.filter.logging.Slf4jLogFilter">
<property name="statementExecutableSqlLogEnable" value="false"/>
</bean>
<!--配置spring整合mybatis相关-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
<!--配置加载SqlMapConfig.xml 如果不配置,则配置别名-->
<!--<property name="configLocation" value="classpath:SqlMapConfig.xml"/>-->
<!--配置别名-->--修改此处
<property name="typeAliasesPackage" value="com.ssm.model"/>
<property name="mapperLocations" value="classpath:com/ssm/mapper/*.xml"/>
</bean>
<!--配置mapper扫描器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入某个包 为该包下面的所有mapper接口动态代理生成实现类-->
<property name="basePackage" value="com.ssm.mapper"/>--修改此处
<!--注入sqlSessionFactory,如果只有1个sqlSessionFactory可以不用注入 多个需要注入 推荐配置-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
</beans>
3.Spring整合Service层
<?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: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 https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="com.ssm.service"/>--修改此处
</beans>
4.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" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--1.配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据库-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
5.配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--配置servlet-->
<servlet-mapping>
<!--设置拦截路径 不拦截jsp-->
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet>
<!--配置spring-mvc前端处理器DispatcherServlet-->
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--指定spring-mvc的配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!--在服务器启动的时候就初始化该servlet-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--配置ContextLoaderListener监听器加载spring的配置文件-->
<!--配置spring监听器来加载spring的配置-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--解决post请求乱码-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--配置form表单支持 put delete的过滤器-->
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--配置druid的sql监控-->
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>druidWebStatFilter</filter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
<init-param>
<param-name>exclusions</param-name>
<param-value>/public/*,*.js,*.css,/druid*,*.jsp,*.swf</param-value>
</init-param>
<init-param>
<param-name>principalSessionName</param-name>
<param-value>sessionInfo</param-value>
</init-param>
<init-param>
<param-name>profileEnable</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>druidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>