Spring框架是一个非常活跃的开源框架, 基于IOC和AOP来构架多层JavaEE系统,以帮助分离项目组件之间的依赖关系,它的主要目地是简化企业开发.
Spring框架包含的功能大约由20个模块组成。这些模块按组可分为核心容器、数据访问/集成,Web,AOP(面向切面编程)、设备、消息和测试
ORM:(Object relation mapping) 对象关系映射 操作数据库的。
Object :java对象 User Product实体类
relation:关系型数据库 数据库表
mapping:映射【xml配置文件】 通过映射把实体类和数据库表进行关联,达到操作实体类能够操作数据库表的目的。User类和user表进行关联。
hibernate就是一个标准的orm框架。
core - 核心模块
详细说明
(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)
(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()
}
<beans>
<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);
}
bean标签:是根标签beans内部必须包含的标签,它是用于声明具体的类的对象!
bean标签对应的属性
Property | 属性解释 |
---|---|
class | 指定bean对应类的全路径 |
name | name是bean对应对象的一个标识 |
scope | 执行bean对象创建模式和生命周期 |
id | id是bean对象的唯一标识,不能添加特别字符 |
lazy-init | 是否延时加载 默认值:false |
init-method | 对象初始化方法 |
destroy-method | 对象销毁方法 |
<beans ......>
<bean name="user" id="user" class="cgp.model.User" scope="singleton" lazy-init="true" init-method="init" destroy-method="des"/>
<bean id="user2" class="cgp.model.UserFactory" factory-method="getUser"/>
<bean id="fac" class="cgp.model.UserFactory"/>
<bean id="user3" factory-bean="fac" factory-method="getUser2"/>
beans>
Dependency Injection,说的是创建对象实例时,同时为这个对象注入它所依赖的属性。相当于把每个bean与bean之间的关系交给容器管理。而这个容器就是spring。
案例步骤:
.准备spring的环境
UserController
UserService UserServiceImpl
UserDao UserDaoImpl
编写配置文件
<beans ...>
<bean id="userController" class="di.UserController">
<property name="userService" ref="userService"/>
bean>
<bean id="userService" class="di.UserServiceImpl">
<property name="userDao" ref="userDao"/>
bean>
<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.1 基本类型值注入使用value
<bean name="person" class="cgp.model.Person">
<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.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的有参构造
导入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">
<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的无参构造
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>
<bean name="person" class="cgp.model.Person" p:car-ref="car">
<property name="name" value="#{car.name}"/>
<property name="age" value="#{person.age}"/>
bean>
<bean id="user" class="cgp.model.User">
<property name="date" value="#{'2012/12/12'}"/>
<property name="date1" value="#{new java.util.Date()}"/>
<property name="uid" value="#{100+12*2}"/>
<property name="name" value="#{car.name}"/>
<property name="password" value="#{T(java.util.UUID).randomUUID().toString()}"/>
bean>
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>数组1value>
<ref bean="car"/>
list>
property>
<property name="list">
<list>
<value>集合1value>
<list>
<value>集合中的集合1value>
<value>集合中的集合2value>
<value>集合中的集合3value>
list>
<ref bean="car"/>
list>
property>
<property name="map">
<map>
<entry key="car" value-ref="car"/>
<entry key="name" value="保时捷"/>
<entry key="age" value="11"/>
map>
property>
<property name="properties">
<props>
<prop key="name">ageprop>
<prop key="age">111prop>
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}}
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的约束,并配置注解扫描
<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")//
public class Person {
private String name;
private Integer age;
private Car car;
public Person(){
System.out.println("无参数构造方法!");
}
//getter,setter,toString
}
这几个注解的功能和@Component 一样。
@Service("person") // service层
@Controller("person") // controller层
@Repository("person") // dao层
指定对象作用域
@Scope(scopeName="singleton") 单例 scopeName也可以换成value
@Scope(scopeName="prototype") 多例
1.设置成员变量上:通过反射给变量赋值
@Value("name值")
private String name;
@Value(“name值”) 等同于 @Value(value=“name值”)
2.加在set方法上:通过set方法赋值
@Value("tom")
public void setName(String name){
this.name = name;
}
使用 @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名字赋值.
@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的实现类!
@Resource 是java的注释,但是Spring框架支持,@Resource指定注入哪个名称的对象
@Resource(“name”) == @Autowired + @Qualifier(“name”)
@Resource(name="baoma")
private Car car;
初始化和销毁方法等同于配置文件添加的init-method和destroy-method功能,
例:Person类中init方法和destroy方法添加如下注解:
@PostConstruct
public void init(){
System.out.println("初始化方法");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法");
}
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语言,提供了一个专门的编译器,在编译时提供横向代码的织入
Srping框架的AOP技术底层也是采用的代理技术,代理的方式提供了两种
必须是面向接口的,只有实现了具体接口的类才能生成代理对象
基于CGLIB动态代理
对于没有实现了接口的类,也可以产生代理,产生这个类的子类的方式
Spring的传统AOP中根据类是否实现接口,来采用不同的代理方式
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);
}
}
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);
}
}
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);
}
}
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:config proxy-target-class="true">
<aop:pointcut id="cut" expression="execution(* aop.service.impl.*.add*(..))"/>
<aop:pointcut id="cut2" expression="execution(* aop.service.impl.*.delete*(..))"/>
<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();
}
}
AspectJ是一个基于Java语言的AOP框架
Spring2.0以后新增了对AspectJ切点表达式支持
@AspectJ 是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面
主要用途:自定义开发
新版本Spring框架,建议使用AspectJ方式来开发AOP
aop联盟定义通知类型,具有特性接口,必须实现,从而确定方法名称。
aspectj 通知类型,只定义类型名称。方法格式。
编写切面类
//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:config>
<aop:pointcut id="cut" expression="execution(* aspectj.service.impl.*.*(..))"/>
<aop:aspect ref="myAdvice">
<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-->最终通知
切面类 aop的配置
<context:component-scan base-package="aspectj_anno"/>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
修改实现类使用注解
修改切面类
//Aspect 声明切面,修饰切面类,从而获得通知
@Component//
@Aspect//
public class MyAdvice {
//配置一个公共的切点表达式
@Pointcut("execution(* aspectj_anno.service.impl.*.*(..))")
public void myCut(){}
//前置通知
@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
Spring JDBC是Spring所提供的持久层技术,它的主要目标是降低使用JDBC API的门槛,以一种更直接,更简介,更简单的方式使用JDBC API, 在Spring JDBC里,仅需做那些与业务相关的DML操作,而将资源获取,Statment创建,资源释放以及异常处理等繁杂而乏味的工作交给Spring JDBC.
虽然ORM的框架已经成熟丰富,但是JDBC的灵活,直接的特性,依然让他拥有自己的用武之地,如在完全依赖查询模型动态产生查询语句的综合查询系统中,Hibernaye,MyBatis,JPA等框架都无法使用,这里JDBC是唯一的选择.
导入相关的包
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");
}
}
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
<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}"/>
<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}"/>
测试类同上
事务的第一个方面是传播行为(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:事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重
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配置文件
<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}"/>
<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:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<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>
<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);
}
}
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();
}
修改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}"/>
<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"/>
测试类同上
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)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 模块
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 模块的功能
(1)spring-aop
提供了面向切面编程(AOP)的实现,可以定义诸如方法拦截器和切入点等,从而使实现功能的代码彻底的解耦。使用源码级的元数据。
(2)spring-aspects
提供了对 AspectJ 的集成
(1)spring-instrument
模块提供了对检测类的支持和用于特定的应用服务器的类加载器的实现。
(2)spring-instrument-tomcat
模块包含了用于 Tomcat 的Spring 检测代理。
spring-messaging 模块
从 Spring 4 开始集成,从一些 Spring 集成项目的关键抽象中提取出来的。这些项目包括 Message、MessageChannel、MessageHandler 和其它服务于消息处理的项目。这个模块也包含一系列的注解用于映射消息到方法
spring-test 模块
通过 JUnit 和 TestNG 组件支持单元测试和集成测试。它提供了一致性地加载和缓存 Spring 上下文,也提供了用于单独测试代码的模拟对象(mock object)
大部分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的领跑者.
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。
MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式:[1]
MVC 模式同时提供了对 HTML、CSS 和 JavaScript 的完全控制。
**Model(模型)**是应用程序中用于处理应用程序数据逻辑的部分。
通常模型对象负责在数据库中存取数据。
**View(视图)**是应用程序中处理数据显示的部分。
通常视图是依据模型数据创建的。
**Controller(控制器)**是应用程序中处理用户交互的部分。
通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。
MVC 分层有助于管理复杂的应用程序,因为您可以在一个时间内专门关注一个方面。例如,您可以在不依赖业务逻辑的情况下专注于视图设计。同时也让应用程序的测试更加容易。
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等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cH9VrPNm-1575040207828)(images/springmvc支持流程图.png)]
从接收请求到响应,Spring MVC框架的众多组件通力配合,各司其职,有条不紊的完成分内工作!在整个框架中,DispatchserServlet处于核心的位置,它负责协调和组织不同组件以完成请求处理以及返回影响工作.和大多数Web MVC框架一样,Spring MVC 通过一个前段的Servlet接收所有请求,并将这些工作委托给其他组件进行处理,DispatcherServlet 就是Spring MVC的前段Servlet。下面对Spring MVC处理请求的整体过程进行详解!
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提供,直接使用。
DispatcherServlet是Spring MVC的"灵魂"和"心脏",它负责接受HTTP请求并协调 Spring MVC的各个组件完成请求处理的工作。和任何Servlet一样,用户必须在web.xml中配置好DispatcherServlet。
DispatcherServlet是前端控制器设计模式的实现,提供spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得Spring的所有好处。
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, |
项目名: spring-mvc-base
maven项目pom配置:
<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.0modelVersion>
<groupId>cgpgroupId>
<artifactId>spring-mvcartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>warpackaging>
<name>spring-mvc Maven Webappname>
<url>http://www.example.comurl>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.1.6.RELEASEversion>
dependency>
dependencies>
<build>
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.*include>
includes>
<filtering>truefiltering>
resource>
<resource>
<directory>src/main/resourcesdirectory>
<includes>
<include>**/*.*include>
includes>
resource>
resources>
<plugins>
<plugin>
<groupId>org.apache.tomcat.mavengroupId>
<artifactId>tomcat7-maven-pluginartifactId>
<version>2.2version>
<configuration>
<port>91port>
<path>/webpath>
<uriEncoding>UTF-8uriEncoding>
<server>tomcat7server>
configuration>
plugin>
plugins>
build>
project>
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring-mvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<filter>
<filter-name>characterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
<init-param>
<param-name>forceEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>characterEncodingFilterfilter-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,以便提高运行性能。 |
<context:component-scan base-package="springmvc1.controller"/>
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="suffix" value=".jsp"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
bean>
@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";
}
}
发布项目,通过浏览器,访问 当前项目对应地址+ /hello即可!
@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";
}
}
可以指定方法对应的请求方式!如果客户端请求的方式和方法设置的方式不同,请求不成功!
//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";
}
<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";
}
开发中,也会碰到请求参数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";
}
我们可以通过此注解,获取路径部分的数据!
@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";
}
@RequestMapping("cookie")
public String cookie(@CookieValue("JSESSIONID")String cookie){
System.out.println("cookie = " + cookie);//cookie = 25427660AB818EEFE1A27FCF71AD95BB
return "index";
}
@RequestHeader注解可以获取请求头中的数据!!
@RequestMapping("header")
public String header(@RequestHeader("User-Agent")String header){
System.out.println("header = " + header);
return "index";
}
通过表达式精准映射请求
@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风格资源地址支持3种匹配符:
代码:
@RequestMapping(value = "/?/*/xx" )
@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下
}
}
Spring MVC中 GET方式不会乱码!
在web.xml配置文件中添加spring自带的Filter设置编码格式
<filter>
<filter-name>characterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
<init-param>
<param-name>forceEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>characterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
通过form表单向指定的controller的方法传递对象!
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()
}
@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";
}
}
文件位置: /WEB-INF/user/form.jsp
name属性要和User对象的属性相同
注意:name的特殊写法,这里可以直接将表单数据转成User对象,但是User对象内部包含 Address的对象,所以,这里可以调用第一层属性,再点一层属性,如果多层依次类推!
user = User{name='chenganpin', age=12, date=Wed May 08 00:00:00 CST 2019, address=Address{province='浙江', city='温州'}}
实现方案有三种: ModelAndView Map Model
往页面传递数据使用request域对象
@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" %>
结果
${requestScope.user}
<%--User{name='cgp', age=18, date=Wed May 08 19:08:27 CST 2019, address=Address{province='浙江', city='温州'}}--%>
${requestScope.user.date}
<%--Wed May 08 19:08:27 CST 2019--%>
<%--2019-05-08--%>
@RequestMapping("data2")
//在方法形参上写上Map 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";//转发
}
页面显示代码同上
将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 {...}
如果我们需要使用Servlet内部常用类:
直接在Controller层的方法中传入对应参数即可!不分顺序!
注意:如果使用maven项目 需要导入 jsp jstl servlet api
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jstlartifactId>
<version>1.2version>
dependency>
<dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>jsp-apiartifactId>
<version>2.2.1-b03version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
<scope>providedscope>
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);
}
}
REST:即Representational State Transfer , (资源)表现层状态转化,是目前最流行的一种互联网软件架构。它结构清晰、符合标注、易于理解、方便扩展,所以越来越多的网站采用!
具体说,就是HTTP协议里面,四个表示操作方式的动词:
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 删除某个指定动物园的指定动物
1.JSP技术可以让我们在页面中嵌入Java代码,但是这样的技术实际上限制了我们的开发效率,因为需要我们Java工程师将html转换为jsp页面,并写一些脚本代码,或者前端代码。这样会严重限制我们的开发效率,也不能让我们的java工程师专注于业务功能的开发,所以目前越来越多的互联网公司开始实行前后端分离。
2.近年随着移动互联网的发展,各种类型的Client层出不穷,RESTful可以通过一套统一的接口为Web,iOS和Android提供服务。另外对于广大平台来说,比如微博开放平台,微信开放平台等,它们不需要有显式的前端,只需要一套提供服务的接口,RESTful无疑是最好的选择。
HiddenHttpMethodFilter:浏览器form表单只支持GET和POST,不支持DELETE和PUT请求,Spring添加了一个过滤器,可以将这些请求转换为标准的http方法,支持GET,POST,DELETE,PUT请求!
web.xml添加HiddenHttpMethodFilter配置, 使form表单支持 put delete的过滤器
<filter>
<filter-name>HiddenHttpMethodFilterfilter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class>
filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilterfilter-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" %>
REST表单
新增用户
删除用户
更新用户
需要注意: 由于doFilterInternal方法只对method为post的表单进行过滤,所以在页面中必须如下设置:
代表post请求,但是HiddenHttpMethodFilter将把本次请求转化成标准的put请求方式! name="_method"固定写法!
需要注意一种,DispatcherServlet拦截资源设置成了 / 避免了死循环,但是 / 不拦截jsp资源,但是它会拦截其他静态资源,例如 html , js , 图片等等, 那么我们在使用jsp内部添加 静态资源就无法成功,所以,我们需要单独处理下静态资源!
修改Spring MVC对应配置文件,添加mvc命名空间和约束
<mvc:resources mapping="/static/**" location="/static/"/>
<mvc:default-servlet-handler/>
配置解释: 将静态资源(图片,css,js,html)放入了webApp/static文件夹下! 直接访问DispatcherServlet会拦截出现404问题!
location元素表示webapp目录下的static包下的所有文件;
mapping元素表示将 location对应文件加下的资源对应到 /static路径下!
该配置的作用是:DispatcherServlet不会拦截以/static开头的所有请求路径,并当作静态资源交由Servlet处理
访问控制器返回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通过HandlerExceptionResolver处理程序的异常,包括处理映射,数据绑定及处理器执行时发生异常。HandlerExceptionResolver仅有一个接口方法:
ModelAndView resolveException(HttpServletRequest reqeust,HttpServletResponse response,Object handler,Exception ex);
当发生异常时,Spring MVC将调用 resolveException()方法,并转到ModelAndView对应视图中,作为一个异常报告页面,反馈给用户!
HandlerExceptionResolver拥有4个实现类:
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>404error-code>
<location>/404.htmllocation>
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配置的时候扫描该类!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-adpWdb4d-1575040207829)(images/springmvc拦截器.png)]
Spring MVC 的拦截器类似于Servlet中的过滤器Filter!需要先定义一个类实现HandlerInterceptor接口!
添加未实现的方法,在springmvc配置中配置!具体实现步骤如下:
拦截器是SpringMVC提供的 不基于web服务器运行,过滤器是基于web服务器的。过滤可以过滤所有请求,
拦截器不拦截jsp文件的。过滤器在web.xml配置。拦截器在springMVC的配置文件里面配置。
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");
}
}
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/user/**/"/>
<bean class="springmvc.inteceptors.MyInteceptor1"/>
mvc:interceptor>
mvc:interceptors>
Spring MVC为文件上传提供了直接支持,这种支持是通过即插即用的MultipartResolver实现. Spring使用Jakarta Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver。
在SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要先在上下文中配置MultipartResolver。
引入jar包
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.4version>
dependency>
<dependency>
<groupId>commons-iogroupId>
<artifactId>commons-ioartifactId>
<version>2.6version>
dependency>
配置文件上传MultipartResolver
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<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>
编写控制器和文件上传表单
@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 本是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语句抽取到配置文件中。
数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。
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编程中对结果的解析处理过程。
maven依赖mybatis mysql-connector-java 相关日志文件
配置SqlMapConfig.xml文件
<configuration>
<typeAliases>
<typeAlias type="cgp.pojo.User" alias="ur"/>
<package name="cgp.pojo"/>
typeAliases>
<environments default="development">
<environment id="development">
<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>
<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文件
<mapper namespace="cgp.pojo.User">
<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>
<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接口开发需要遵循以下规范:
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的类型一致
定义mapper映射文件UserMapper.xml(内容同Users.xml),需要修改namespace的值为 UserMapper接口路径。将UserMapper.xml放在classpath 下mapper目录 下。
<mapper namespace="cgp.mapper.UserMapper">
<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>
<select id="selByPage" parameterType="map" resultType="user">
select * from user limit #{begin},#{limit}
select>
<select id="selByPage2" parameterType="expanduser" resultType="user">
select * from user limit #{begin},#{limit}
select>
<select id="selByPage3" resultType="user" parameterType="expanduser">
select * from user limit #{param1},#{param2}
select>
<select id="selByPage4" resultType="user">
select * from user limit #{begin},#{limit}
select>
mapper>
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);
}
修改SqlMapConfig.xml文件:
<mappers>
<package name="cgp.mapper"/>
mappers>
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);
}
}
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
<!--起别名来解决属性名和字段名不一致-->
<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>
通过mybatis提供的各种标签方法实现动态拼接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>
<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>
<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=1otherwise>
choose>
where>
select>
<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>
<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>
<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 usersql>
<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元素设计的初衷,就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。
<mapper namespace="associationmapping.mapper.UserMapper">
<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>
<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>
<resultMap id="OrderAndUserMap" type="OrdersExpand" extends="orderMap">
<association property="user" javaType="user" resultMap="userMap"/>
resultMap>
<select id="selOrdersWithUser2" resultMap="OrderAndUserMap2">
select * from orders
select>
<resultMap id="OrderAndUserMap2" type="OrdersExpand" extends="orderMap">
<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>
<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 property="list" ofType="Orders" resultMap="orderMap"/>
resultMap>
<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);
}
}
}
整合的准备工作
web.xml
log4j.properties
db.properties
spring-mvc.xml
applicationContext-service.xml
applicationContext-tx.xml
applicationContext-dao.xml
SqlMapConfig.xml(可不配置)
Sql映射文件
pom文件:ssm 其他的工具
<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: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>
<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"/>
<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"/>
<property name="validationQuery" value="select 1 "/>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="false"/>
<property name="poolPreparedStatements" value="false"/>
<property name="maxPoolPreparedStatementPerConnectionSize"
value="20"/>
<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>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
--修改此处
<property name="typeAliasesPackage" value="com.ssm.model"/>
<property name="mapperLocations" value="classpath:com/ssm/mapper/*.xml"/>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.ssm.mapper"/>--修改此处
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
bean>
beans>
<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>
<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">
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
beans>
<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-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring-mvc.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:applicationContext-*.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<filter>
<filter-name>characterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
<init-param>
<param-name>forceEncodingparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>characterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<filter-mapping>
<filter-name>characterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<filter>
<filter-name>HiddenHttpMethodFilterfilter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class>
filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<servlet>
<servlet-name>DruidStatViewservlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>DruidStatViewservlet-name>
<url-pattern>/druid/*url-pattern>
servlet-mapping>
<filter>
<filter-name>druidWebStatFilterfilter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilterfilter-class>
<init-param>
<param-name>exclusionsparam-name>
<param-value>/public/*,*.js,*.css,/druid*,*.jsp,*.swfparam-value>
init-param>
<init-param>
<param-name>principalSessionNameparam-name>
<param-value>sessionInfoparam-value>
init-param>
<init-param>
<param-name>profileEnableparam-name>
<param-value>trueparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>druidWebStatFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
web-app>