Spring开发框架代码demo下载-网盘地址
- 链接:https://pan.baidu.com/s/1Q8t9sE0TP2QaA5fuB723SA
- 提取码:6wda
Spring框架完整讲解 (持续更新中)
spring框架介绍
Spring是一个轻量级的对象管理的容器框架。在Java项目开发中都需要对项目进行分层开发,最为代表的就是MVC开发模式:即分为显示层(view)、控制层(control)、模型层(model)。
- 显示层:有前端框架:可以快速的帮助开发者构建页面,完成数据的渲染。
- 控制层:有框架通过简短的配置、代码编写,就可以方便快速的映射到相应的方法中,并且可以自动的进行数据的接收、验证、类型、实体类转换。
- 模型层:包含业务层和数据层,数据层有许多的ORM设计框架,帮助开发者无需关注JDBC的操作代码,就可实现数据的增删改查操作。
可以发现显示层、控制层、数据层都有相应的开发框架来辅助开发,使其达到简便,只有业务层的操作,还是需要开发者手动的完成相应操作:
- 手动获取数据库的连接。
- 如果是查询需要手动关闭事务的处理操作。
- 调用数据层完成数据的增删改查,如果发生错误,需要手动捕捉异常,对操作的数据手动进行回滚。
- 执行完毕后手动进行事务的提交操作,手动关闭数据库。
Spring就是用于辅助业务层的开发,使其开发者只需要关注核心的业务处理,而与业务无关的功能,由spring来处理,如数据库的连接、事务的处理、层与层之间的交互
spring架构
Spring的架构如上图:
-
Core Container:spring核心功能:
- Beans:用于对象的管理,对应关系配置
- Core:核心功能操作,比如依赖注入,资源读取
- Context:对象上下文的生命周期监听
- SpEL:spring提供的表达式语言操作
-
AOP:切面编程,完成辅助性的操作:
- Aspects:切面编程的语法支持
- Instrumentation:监听JVM的动态操作
- Messaging:消息处理支持
-
Data Access/Integration:
- JDBC:spring提供的基于ormapping设计的jdbc框架
- ORM:可以方便整合第三方的数据层开发框架
- OXM:表示对象和xml文件的转换
- JMS:消息组件支持
- Transactions:spring提供的事务处理操作
- Web:方便整合第三方的web开发框架,也提供有spring自己的mvc实现方式。
控制反转(IOC)
控制反转就是所有对象实例化不需要关键字new来声明对象,通过spring提供的Beans对象管理操作来自动实例化对象,也就是一个工厂类操作。
要想使用spring开发框架,就需要为其引用相关开发jar包。本次只引入ioc相关操作包。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.1</version>
</dependency>
引入相关开发包之后,定义一个接口以及实现子类
package com.txp.ioc;
public interface IMemberService {
boolean addMember();
}
// 实现子类
package com.txp.ioc.impl;
import com.txp.ioc.IMemberService;
public class MemberServiceImpl implements IMemberService {
@Override
public boolean addMember() {
System.out.println("增加用户信息");
return false;
}
}
按照传统的代码编写方式:要想获取该接口的对象,通过关键字new和对象的多态性完成。但是这样的操作存在有严重的耦合问题,明确的表示了具体的实例化子类。
IMemberService memberService = new MemberServiceImpl();
memberService.addMember();
范例:通过spring配置文件获取实例化对象
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义实例化子类 -->
<bean id="memberServiceImpl" class="com.txp.ioc.impl.MemberServiceImpl"/>
</beans>
配置好相应的子类之后,要向获取该对象,需要通过spring容器获得。Spring提供启动容器的一个执行标准接口:
org.springframework.context.ApplicationContext
要想获取该接口的实例,需要依赖于子类 :
- 通过classpath加载:
- org.springframework.context.support.ClassPathXmlApplicationContext
- 通过文件所属本地路径加载:
- org.springframework.context.support.FileSystemXmlApplicationContext
public class TestMemberService {
public static void main(String[] args) {
// 启动容器
ApplicationContext act = new ClassPathXmlApplicationContext("src/main/resources/applicationContext.xml");
// 根据id获取容器实例化对象
IMemberService memberService = (IMemberService) act.getBean("memberServiceImpl");
memberService.addMember();
}
}
依赖注入(DI)
依赖注入就是spring容器在进行对象实例化的时候,进行各种依赖的配置,简单理解就是:为对象中的属性设置值。
根据setter/getter方法注入
- 提供一个实体类,完善好setter和getter方法
package com.txp.vo;
public class Person{
private Integer id;
private String name;
private Integer age;
private String address;
// setter / getter 略
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", age=" + age + ", address=" + address + "]";
}
}
- 在spring配置文件中配置此类信息,进行实例化操作
配置文件描述:- bean: 标签bean表示配置要注入的类
- id: 定义获取注入对象的名称,此名称必须唯一
- class: 要注入类所在的完整路径 包.类
- property:根据setter方法注入
- name: 表示属性名称
- value: 表示属性的值
- ref: 配置引用传递对象
- bean: 标签bean表示配置要注入的类
范例:1、根据setter方法实现注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.txp.vo.Person">
<property name="id" value="12345"/>
<property name="name" value="Taylor Swift"/>
<property name="age" value="31"/>
<property name="address" value="美国xxxx地方"/>
</bean>
</beans>
范例:2、配置类之间的引用关系
在日常开发中,不可能只针对一个类进行操作。那么类的关系也会有一对一、一对多、多对多,这样的关系。这里我们定义一个汽车类,然后上之前定义的Person类中加入此类属性并添加getter&setter方法。使用ref配置引用。
// 创建一个汽车类
package com.txp.vo;
public class Car {
private String brand_title;
// setter / getter 略
@Override
public String toString() {
return "Car [brand_title=" + brand_title + "]";
}
}
// 在Person类中设置对应关系
private Car car;
public void setCar(Car car) {
this.car = car;
}
public Car getCar() {
return car;
}
// 在applicationContext文件中增加一个新的bean对象
<bean id="car" class="com.txp.vo.Car">
<property name="brand_title" value="大众"/>
</bean>
// 修改Person配置
<bean id="person" class="com.txp.vo.Person">
<property name="id" value="12345"/>
<property name="name" value="Taylor Swift"/>
<property name="age" value="31"/>
<property name="address" value="美国xxxx地方"/>
<!-- 配置引用关系 -->
<property name="car" ref="car"/>
</bean>
内部bean的配置
<bean id="person" class="com.txp.vo.Person">
<property name="id" value="12345"/>
<property name="name" value="Taylor Swift"/>
<property name="age" value="31"/>
<property name="address" value="美国xxxx地方"/>
<!-- 配置引用关系 -->
<property name="car" ref="car"/>
<!-- 通过配置内部bean来完成依赖注入 -->
<property name="child">
<bean class="com.txp.vo.Child">
<property name="name" value="孩子一"/>
<property name="age" value="30"/>
</bean>
</property>
</bean>
通过构造方法注入
package com.txp.vo;
public class Dept {
private Integer id;
private String dname;
private String loc;
public Dept(String dname, String loc, Integer id) {
this.dname = dname;
this.loc = loc;
this.id = id;
}
@Override
public String toString() {
return "Dept [id=" + id + ", dname=" + dname + ", loc=" + loc + "]";
}
}
这里我定义了两个属性,分别为dname、loc。构造方法注入有如下几种配置方式:
- 根据构造方法所接收参数顺序注入
<bean id="dept" class="com.txp.vo.Dept">
<constructor-arg value="测试部"/> <!-- dname -->
<constructor-arg value="所属位置xxx"/> <!-- loc -->
<constructor-arg value="12"/>
</bean>
- 通过属性的角标注入:
index:属性表示构造方法中接收参数属性的角标,从0开始。
<bean id="dept" class="com.txp.vo.Dept">
<constructor-arg index="1" value="上海"/> <!-- loc -->
<constructor-arg index="0" value="开发部"/> <!-- dname -->
<constructor-arg index="2" value="34"/> <!-- id -->
</bean>
- 根据构造方法接口参数名称注入:
name:构造方法中接收的参数名称匹配
<bean id="dept" class="com.txp.vo.Dept">
<constructor-arg name="loc" value="上海"/>
<constructor-arg name="dname" value="开发部"/>
<constructor-arg name="id" value="562"/>
</bean>
- 根据接收类型注入:
type:构造方法中参数类型匹配
<bean id="dept" class="com.txp.vo.Dept">
<constructor-arg type="java.lang.Integer" value="23"/>
<constructor-arg type="java.lang.String" value="开发"/>
<constructor-arg type="java.lang.String" value="上海"/>
</bean>
- 通过构造方法注入对象
ref:注入配置的bean对象
<bean id="person" class="com.txp.vo.Person">
<property name="id" value="12345"/>
<property name="name" value="Taylor Swift"/>
<property name="age" value="31"/>
<property name="address" value="美国xxxx地方"/>
<property name="car" ref="car"/>
<property name="child">
<bean class="com.txp.vo.Child">
<property name="name" value="孩子一"/>
<property name="age" value="30"/>
</bean>
</property>
</bean>
<bean id="dept" class="com.txp.vo.Dept">
<constructor-arg name="id" value="23"/>
<!-- 根据id获取配置的Person对象 -->
<constructor-arg name="person" ref="person"/>
</bean>
- 定义参数别名,根据name匹配别名注入。
要想定义参数别名,需要在构造方法上使用注解@ConstructorProperties注解。
// biem1 = dname
// biem2 = loc
// biem3 = id
@ConstructorProperties(value = {"biem1", "biem2", "biem3"})
public Dept(String dname, String loc, Integer id) {
this.dname = dname;
this.loc = loc;
this.id = id;
}
// 根据别名注入
<bean id="dept" class="com.txp.vo.Dept">
<constructor-arg name="biem3" value="23"/>
<constructor-arg name="biem1" value="开发"/>
<constructor-arg name="biem2" value="上海"/>
</bean>
注入集合
Java中常用的集合:Array、List、Set、Map、Properties。
范例:
public class Emp {
private Integer id;
private List<String> skills;
// setter getter 略
}
public class Dept {
private String dname;
private String strs[];
private List<Emp> emps;
private Set<Integer> empids;
private Map<String, Object> tagsMap;
private Properties pros;
// setter getter 略
}
public class Tags {
private Integer tagId;
private String tagName;
// setter getter 略
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="emp" class="com.txp.di.Emp">
<property name="id" value="1234"/>
<!-- 注入List集合 -->
<property name="skills">
<list>
<value>弹吉他</value>
<value>英语</value>
<value>Oracle Database</value>
<value>Java</value>
</list>
</property>
</bean>
<bean id="emp2" class="com.txp.di.Emp">
<property name="id" value="2234"/>
<!-- 注入List集合 -->
<property name="skills">
<list>
<value>唱歌</value>
<value>韩语</value>
<value>MySQL Database</value>
<value>Python</value>
</list>
</property>
</bean>
<bean id="dept" class="com.txp.di.Dept">
<property name="dname" value="开发部"/>
<!-- 注入Array -->
<property name="strs">
<array>
<value>数组一</value>
<value>数组二</value>
<value>数组三</value>
<value>数组四</value>
</array>
</property>
<!-- 注入list集合 -->
<property name="emps">
<list>
<ref bean="emp"/>
<ref bean="emp2"/>
</list>
</property>
<!-- 注入set集合 -->
<property name="empids">
<set>
<value>1234</value>
<value>2234</value>
</set>
</property>
<!-- 注入map -->
<property name="tagsMap">
<map>
<!-- 注入方式一 -->
<entry key="tag1">
<value>tag1标签的描述</value>
</entry>
<!-- 注入方式二 -->
<entry key="tag2" value="tag2标签的描述"/>
<!-- 注入对象 -->
<entry key="tags" value-ref="tags"/>
</map>
</property>
<!-- 注入properties -->
<property name="pros">
<props>
<prop key="add.dept">增加一个Dept对象</prop>
<prop key="add.emp">增加一个Emp对象</prop>
</props>
</property>
</bean>
<bean id="tags" class="com.txp.di.Tags">
<property name="tagId" value="98"/>
<property name="tagName" value="测试"/>
</bean>
</beans>
Bean对象管理
spring的负责帮助我们进行对象的实例化管理操作,而spring默认是使用单例的设计模式,无论我们调用对象多少次,最终都只实例化一个对象。
范例:通过spring进行对象的实例化,观察结果。
public class BeanDemo {
private String name;
private String content;
// setter getter 略
// 构造方法
public BeanDemo() {
System.out.println("====== 构造方法初始化 =======");
}
}
<bean id="beanDemo" class="com.txp.di.BeanDemo">
<!-- scope="singleton"是默认的,写不写都可以 -->
<property name="name" value="测试" scope="singleton"/>
<property name="content" value="内容内容内容内容"/>
</bean>
// 创建5个线程,5次调用实例化的对象
public class TestDemo {
public static void main(String[] args) {
ApplicationContext act = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
for(int x = 0 ; x < 5 ; x ++) {
new Thread(() -> {
BeanDemo beanDemo = act.getBean("beanDemo", BeanDemo.class);
System.out.println("【当前线程名称】" + Thread.currentThread().getName() + "\t" + beanDemo);
}).start();
}
}
}
// 输出结果
====== 构造方法初始化 =======
【当前线程名称】Thread-1 对象地址: com.txp.di.BeanDemo@7fdbc0BeanDemo [name=测试, content=内容内容内容内容]
【当前线程名称】Thread-2 对象地址: com.txp.di.BeanDemo@7fdbc0BeanDemo [name=测试, content=内容内容内容内容]
【当前线程名称】Thread-3 对象地址: com.txp.di.BeanDemo@7fdbc0BeanDemo [name=测试, content=内容内容内容内容]
【当前线程名称】Thread-4 对象地址: com.txp.di.BeanDemo@7fdbc0BeanDemo [name=测试, content=内容内容内容内容]
【当前线程名称】Thread-0 对象地址: com.txp.di.BeanDemo@7fdbc0BeanDemo [name=测试, content=内容内容内容内容]
可以发现,我们创建5个线程来调用时,发现我们调用对象中的构造方法只输出了一次,也就是说,spring是采用单例的设计模式,来进行对象管理的。
如果要想取消spring默认的单例设计,需要将scope设置为scope=“prototype”。
范例:取消单例
<bean id="beanDemo" class="com.txp.di.BeanDemo" scope="prototype">
<property name="name" value="测试"/>
<property name="content" value="内容内容内容内容"/>
</bean>
// 执行结果
====== 构造方法初始化 =======
====== 构造方法初始化 =======
====== 构造方法初始化 =======
====== 构造方法初始化 =======
====== 构造方法初始化 =======
【当前线程名称】Thread-2 对象地址: com.txp.di.BeanDemo@759ce0cbBeanDemo [name=测试, content=内容内容内容内容]
【当前线程名称】Thread-1 对象地址: com.txp.di.BeanDemo@7650e297BeanDemo [name=测试, content=内容内容内容内容]
【当前线程名称】Thread-3 对象地址: com.txp.di.BeanDemo@4a27a27dBeanDemo [name=测试, content=内容内容内容内容]
【当前线程名称】Thread-0 对象地址: com.txp.di.BeanDemo@4efd9318BeanDemo [name=测试, content=内容内容内容内容]
【当前线程名称】Thread-4 对象地址: com.txp.di.BeanDemo@26b20eb7BeanDemo [name=测试, content=内容内容内容内容]
P标签使用
之前想属性中注入内容,需要声明一个bean标签,在配置相应的property,使用p标签可以减少代码的编写。要想使用p标签,需要在spring配置文件中引入p标签的命名空间。
范例:使用p标签
public class PDemo {
private String name;
private String content;
// setter getter 略
}
<?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: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 id="pdemo" class="com.txp.di.PDemo" p:name="哈哈" p:content="内容部分"/>
</beans>
自动装配
自动装配就是自动找到spring实例化的对象,使用Autowired注解或者在配置文件中配置。
自动装配有两种寻找方式:默认根据类型自动匹配,如果根据类型没有找到,则根据名称进行寻找。
- 根据类型寻找
- 根据名称寻找
范例:根据类型自动匹配注入
public class Emp {
private Integer empno;
private String ename;
// setter getter 略
}
// Dept中有多个Emp
public class Dept {
private String dname;
private String loc;
private List<Emp> empList;
// setter getter 略
}
// 配置emp和dept对象
<bean id="emp" class="com.txp.di.Emp" p:empno="1204" p:ename="谭旭鹏"/>
// autowire="byType"会根据保存的类型自动注入上面的emp
<bean id="dept" class="com.txp.di.Dept" p:dname="开发部" p:loc="上海" autowire="byType"/>
如果此时,配置文件中有两个emp对象,那么根据类型自动匹配的话,spring是无法找到的。如下代码
// 修改Emp类,增加Dept对象,一个员工对应一个部门
private Dept dept;
// 在emp中设置自动装配,根据类型匹配,此时会发生错误,
// 因为这里定义了两个dept对象,emp无法确认是哪一个
<bean id="emp" class="com.txp.di.Emp" p:empno="1204" p:ename="谭旭鹏" autowire="byType"/>
<bean id="dept" class="com.txp.di.Dept" p:dname="开发部" p:loc="上海"/>
<bean id="dept2" class="com.txp.di.Dept" p:dname="测试部" p:loc="北京"/>
要想解决这样的问题,需要设置自动推出和推荐:
1、autowire-candidate=“true”:自动推荐
2、autowire-candidate=“false”:自动推出
<bean id="dept" class="com.txp.di.Dept" p:dname="开发部" p:loc="上海" autowire-candidate="true"/>
<bean id="dept2" class="com.txp.di.Dept" p:dname="测试部" p:loc="北京" autowire-candidate="false"/>
范例:根据名称自动匹配注入 autowire=“byName”,实体类中叫empList,会根据名称自动匹配上面的bean。
<bean id="empList" class="com.txp.di.Emp" p:empno="3504" p:ename="周杰伦"/>
<bean id="dept" class="com.txp.di.Dept" p:dname="开发部" p:loc="上海" autowire="byName"/>
自定义初始化和销毁
在spring进行我们Bean对象管理的时候,我们可以配置在每一次调用的时候执行初始化方法和结束方法。
范例:配置方法的初始化和销毁
public class Demo {
public Demo() {
System.out.println("========= 构造方法 =========");
}
// 初始化方法
public void init_method() {
System.out.println("********* 初始化 *******************");
}
// 调用方法
public void run_method() {
System.out.println("********* 执行方法 ******************");
}
// 销毁方法
public void destory_method() {
System.out.println("*********** 销毁 *********************");
}
}
// spring配置
// init-method="init_method" 初始化执行的方法名称
// destroy-method="destory_method" 销毁执行的方法名称
<bean id="demo" class="com.txp.Demo" init-method="init_method" destroy-method="destory_method"/>
// 测试
@ContextConfiguration("classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TestDemo {
@Autowired
private Demo demo;
@Test
public void testDemo() {
this.demo.run_method();
}
}
// 运行结果
========= 构造方法 =========
********* 初始化 *******************
********* 执行方法 ******************
*********** 销毁 *********************
扫描注入
注入就是利用spring框架实现对象间的引用关系,如果利用Spring的配置文件在xml文件中配置,这样过于麻烦。所以spring提供了相关的注解,可以通过注解的形式实现对象的自动配置。
如下注解:
注解名称 | 注解描述 |
---|---|
@Component | 申明一个Bean对象,就像spring配置文件中的 |
@Repository | 申明Bean对象,用于数据层(dao) |
@Service | 申明Bean对象,用于业务层(service) |
@Controller | 申明Bean对象,用于控制层(controller) |
@Repository、@Service、@Controller这三个注解底层使用的都是@Component注解,三个注解没有区别,只是用来表示不同的层。
要想使用注解的方式来进行对象的注入,就需要在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: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-4.3.xsd">
<!-- 配置扫描包 -->
<context:component-scan base-package="com.txp"/>
</beans>
范例:通过注解实现注入
这里我们业务层和数据层,并分别用不同的注解来表示,当业务层要想调用数据层,通过自动装配获取数据层的对象。完成数据层的调用。
// 定义一个数据层用户接口
public interface IUserDAO {
boolean doCreateUser();
}
// 在其实现的子类上加入@Repository注解
// 和spring配置文件中配置的<bean class="UserDAOImpl"></bean>一样
@Repository
public class UserDAOImpl implements IUserDAO{
@Override
public boolean doCreateUser() {
System.out.println("======== 数据层:sql增加==========");
return false;
}
}
// 定义业务层用户接口
public interface IUserService {
boolean addUser();
}
// 在其实现子类上加入@Service注解
@Service
public class UserServiceImpl implements IUserService{
@Autowired
private IUserDAO userDAO;
@Override
public boolean addUser() {
System.out.println("*********** 增加业务数据层 ***********************");
// 调用数据层
this.userDAO.doCreateUser();
return false;
}
}
// 测试类
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class TestUserService {
@Autowired
private IUserService userService;
@Test
public void test() {
this.userService.addUser();
}
}
// 输出结果
*********** 增加业务数据层 ***********************
======== 数据层:sql增加==========
资源文件读取
java原生开发包中只提供的标准输入与输出操作IO操作尤为繁琐。如果要想使用原生的读取classpath环境下的文件,需要对其先进行资源的定位,如果需要读取网络资源,则需要先进行网络方面的加载,才可以读取文件。
要想使用spring提供的io操作,需要引入spring的核心开发包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
spring提供了org.springframework.core.io.Resource接口来进行多种文件的读取,使用其不同的实现子类可以方便的进行:本地文件读取,classpath读取,内存读取,和网络资源读取操作。
- Resource实现子类:
1、ByteArrayResource(内存读取)
2、ClassPathResouce(Classpath加载)
3、FileSystemResource(本地文件资源)
4、UrlResource(网络资源)
5、ServletContextResource(Servlet上下文资源)
范例:内存读取
public class ByteArrayResourceDemo {
public static void main(String[] args) throws Exception{
Resource resource = new ByteArrayResource("内存读取范例".getBytes());
ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
int data = 0;
InputStream input = resource.getInputStream();
while((data = input.read()) != -1) {
byteOutStream.write(data);
}
System.out.println(new String(byteOutStream.toByteArray()));
input.close();
byteOutStream.close();
}
}
范例:Classpath读取
public class ClassPathDemo {
public static void main(String[] args) throws Exception{
Resource resource = new ClassPathResource("spring-base.xml");
Scanner scan = new Scanner(resource.getInputStream());
while(scan.hasNext()) {
System.out.println(scan.nextLine());
}
scan.close();
}
}
范例:本地文件读取
public class FileResourceDemo {
public static void main(String[] args) throws Exception{
Resource resource = new FileSystemResource(new File("C:\\Users\\Chip_\\Documents\\short.txt"));
Scanner scan = new Scanner(resource.getInputStream());
while(scan.hasNext()) {
System.out.println(scan.next());
}
scan.close();
}
}
范例:网络资源文件读取
public class UrlResourceDemo {
public static void main(String[] args) throws Exception{
String basePath = "http://www.baidu.com";
Resource source = new UrlResource(basePath);
Scanner scan = new Scanner(source.getInputStream());
while(scan.hasNext()) {
System.out.println(scan.nextLine());
}
scan.close();
}
}
现在使用Resource接口来读取相应的文件时,必须通过实例化具体子类来实现调用,这样就会产生耦合文件,与Spring自身的理念脱离,所以Spring进一步优化提供了ResourceLoader接口。
ResourceLoader接口是根据字符串的形式来读取文件的。读取不同的文件,通过传入不同的字符串,spring会自动帮我们解析字符串内容。
- ResourceLoader接口实现子类:
- DefaultResourceLoader
1、文件读取: file:文件路径
2、CLASSPATH资源文件读取: classpath:资源文件路径
3、网络资源: http://网路资源路径
- DefaultResourceLoader
范例:读取本地文件
public class ResourceLoaderFileSystemResource {
public static void main(String[] args) throws Exception{
ResourceLoader resourceLoader = new DefaultResourceLoader();
InputStream input = resourceLoader.getResource("file:C:\\\\Users\\\\Chip_\\\\Documents\\\\short.txt").getInputStream();
Scanner scan = new Scanner(input);
while(scan.hasNext()) {
System.out.println(scan.nextLine());
}
scan.close();
}
}
范例:读取classpath环境中文件
public class ResourceLoaderClassPathResource {
public static void main(String[] args) throws Exception{
ResourceLoader resourceLoader = new DefaultResourceLoader();
InputStream input = resourceLoader.getResource("classpath:spring-base.xml").getInputStream();
Scanner scan = new Scanner(input);
while(scan.hasNext()) {
System.out.println(scan.nextLine());
}
scan.close();
}
}
范例:读取网络资源文件
public class ResourceLoaderUrlResource {
public static void main(String[] args) throws Exception{
ResourceLoader resourceLoader = new DefaultResourceLoader();
InputStream input = resourceLoader.getResource("url:http://www.baidu.com").getInputStream();
Scanner scan = new Scanner(input);
while(scan.hasNext()) {
System.out.println(scan.nextLine());
}
scan.close();
}
}
Spring路径通配符:
在日常开发中,对于配置文件的命名是有一定的规范,如:
- com/txp/spring/spring-tx.xml
- com/txp/spring/spring-mvc.xml
- com/app/spring/applicationContext.xml
- com/txp/message/msg.properties
比如现在我只写一段代码读取spring目录下的两个文件:spring-tx.xml、spring-mvc.xml。不可能写两个不同的代码来分别加载不同的文件。肯定是通过自动匹配文件名称来进行加载。
- spring中提供了如下三种通配字符:
- ?:表示匹配零位或一位任意字符
- *:表示匹配零位或一位或多位任意字符
- **:表示匹配任意路径的名称
通过通配符来获取文件,肯定是包含多个或者单个的,那么就需要使用数组来表示,这里不能使使用List或者Set类集合。
如果匹配的是classpath环境下的的多个文件,需要在classpath后面加入*表示匹配多个文件。
范例:找到spring-任意单个或者多个字符的.xml文件 。
- spring-*.xml
范例:找到spring-任意单个字符的.xml文件
- spring-?.xml
- spring-te?t.xml
范例:如下目录:找到任意spring目录下面的任意目录所有的xml文件
- com/txp/spring/config/data.xml
- com/txp/spring/jdbc/spring-jdbc.xml
- com/txp/spring/**/*.xml
Spring表达式
spring表达式增强了字符串的处理操作,让字符串处理操作更加灵活。要想使用spring提供的表达式操作,需要引入如下开发包依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
范例:观察spring表达式
// 字符串转换大写操作
public class SPELDemo {
public static void main(String[] args) {
// 定义一个表达式要解析的字符串
String expString = "(\"Hello\" + \"Word!!!!!!\").toUpperCase()";
System.out.println("【表达式】" + expString);
// 定义表达式的解析器
SpelExpressionParser parser = new SpelExpressionParser();
// 解析给定的表达式
Expression expression = parser.parseExpression(expString);
// 执行解析完成后的表达式
StandardEvaluationContext context = new StandardEvaluationContext();
// 表达式执行完成后的内容
String value = expression.getValue(context, String.class);
System.out.println("【表达式】" + value);
}
}
// 如下输出
【表达式】("Hello" + "Word!!!!!!").toUpperCase()
【表达式】HELLOWORD!!!!!!
如上代码,就完成了一个表达式的基本操作。spring表达式解析有如下几流程:
范例:表达式分隔符
在进行表达式解析式,通常都是对一段字符串中,包含有一些特殊标识的字符进行解析和内容替换、变量设置。#{10+20}、$[30-10],在spring表达式中,如果要想在项目代码中进行此类标记的定义,可以使用ParserContext接口进行定义。
public class SpelParserContextDemo02 {
public static void main(String[] args) {
// 定义一个表达式语言
String expString = "$[10 + 45 + 89]";
System.out.println("【表达式】" + expString);
// 定义表达式的解析器
SpelExpressionParser parser = new SpelExpressionParser();
// 解析给定的表达式
Expression expression = parser.parseExpression(expString, new ParserContext() {
@Override
public boolean isTemplate() {
return true;
}
@Override
public String getExpressionSuffix() {
return "]";
}
@Override
public String getExpressionPrefix() {
return "$[";
}
});
// 执行解析完成后的表达式
StandardEvaluationContext context = new StandardEvaluationContext();
// 表达式执行完成后的内容
int value = expression.getValue(context, Integer.class);
System.out.println("【表达式】" + value);
}
}
范例:设置表达式变量
之前定义表达式时,直接定义要解析的字符串,那如果表达式中存在有变量,则使用 “ # ” 表示。
public class SpelVariableDemo01 {
public static void main(String[] args) {
// 定义一个表达式语言
String expString = "#{#var1 + #var2}";
System.out.println("【表达式】" + expString);
// 定义表达式的解析器
SpelExpressionParser parser = new SpelExpressionParser();
// 解析给定的表达式
Expression expression = parser.parseExpression(expString, ParserContext.TEMPLATE_EXPRESSION);
// 执行解析完成后的表达式
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("var1", 10);
context.setVariable("var2", 30);
// 表达式执行完成后的内容
System.out.println("【表达式】" + expression.getValue(context));
}
}
spring表达式操作还有以下表达式处理操作:
1. 字面表达式(literal):简单的程序操作,如字符拼接
2. 数学表达式(mathematics):+、 -、 *、 %、 mod、 div
3. 关系表达式(relationship):==(eq)、!=(ne)、>(gt)、>=(ge)、<(lt)、<=(le)、范围查询(BETWEEN{最大值,最小值})
4. 逻辑表达式(logic):&&、and、&、||、or、!、not
5. 三目运算符
6. 字符串表达式(string)
7. Class表达式(expreclass)
8. 表达式变量(bariable):表达式变量中,所有的变量都需要使用#来声明
9. 方法引用(quote)
10. 集合表达式(gather)
以上见范例demo:
链接:https://pan.baidu.com/s/14MsMkansfCnjyjrscrg5eg
提取码:daru
aop切面编程
aop就是动态代理设计,在aop操作中,开发者只需要关注业务的本身,而与业务无关的操作则进行动态的程序切面控制,在不破坏原有代码的结构基础上,利用织入的形式来实现代码的动态控制。
aop核心概念:
- 关注点:所有可能需要关注的业务
- 关注分离点:将所有的业务进行拆分,形成一个个独立的个体,将业务和辅助性的功能抽取出来。
- 横切关注点:实现所有的代理功能,利用代理功能可以在多个辅助操作上对多个关注点进行处理,横切点可能有多个。
- 织入:利用横切点表达式确定所有与之辅助的独立模块合并在在一个完整的业务当中
假如我们需要一个根据用户手机号通过短信验证码实现登录的功能,如果此用户没有注册过,则登录时直接进行账号的注册,随后登录,登录成功后并记录最后一次的登录时间,并记录日志。
通过aop表达如下图:将所有与可拆分出来的业务独立出来,利用织入整合起来完成整个业务操作。
aop配置结构:
<aop:config> // AOP定义
<aop:pointcut/> // 定义切入点
<aop:advisor/> // 定义advisor
<aop:aspect> // 定义切面开始
<aop:pointcut/> // 定义切入点
<aop:before/> // 前置通知
<aop:after-returning/> // 后置返回通知
<aop:after-throwing/> // 后置异常通知
<aop:after/> // 后置最终通知
<aop:around/> // 环绕通知
<aop:declare-parents/> // 引入定义
</aop:aspect> // 定义切面开始
</aop:config> // AOP定义
Spring切入点语法:
- execution(注解配置?修饰符配置?方法返回类型 操作类型匹配 方法名称(参数匹配) 异常匹配)
注解配置和修饰符配置是可选的。 - execution(public * com.txp.spring.aop….(…))
要想使用spring的aop代理,需要引入aop的语法支持包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
范例:使用aop代理完成业务的执行操作
// 实体类
public class Dept implements Serializable {
private Long dept_id;
private String dept_name;
private String dept_loc;
// setter getter 略
}
// 定义接口
public interface IDeptService {
boolean add(Dept dept) ;
}
// 实现子类
@Service
public class DeptServiceImpl implements IDeptService {
@Override
public boolean add(Dept dept) {
System.out.println("增加操作 dept=" + dept);
System.out.println("制造异常");
System.out.println(5 / 0);
return false;
}
}
// 定义要执行的代理操作
public class ServiceAdvise {
public void beforeHandle(Object param) {
System.out.println("业务调用之前param=" + param);
}
public void afterHandle() {
System.out.println("业务调用之后");
}
public void afterThrowHandle(Exception e) {
System.out.println("后置异常返回通知 e=" + e);
}
public void afterReturnHandle(Object result) {
System.out.println("后置返回通知 result=" + result);
}
public Object aroundHandle(ProceedingJoinPoint point) {
System.out.println("环绕通知:方法执行之前" + Arrays.toString(point.getArgs()));
Object result = null;
try {
result = point.proceed(point.getArgs());
System.out.println("环绕通知:方法执行完毕返回值 result=" + result);
} catch (Throwable e) {
System.out.println("环绕通知:产生异常" + e);
}
System.out.println("--------------------------------------------");
return result;
}
}
// 测试`在这里插入代码片`
@Test
public void testDept() {
Dept dept = new Dept();
dept.setDept_id(1232L);
dept.setDept_name("开发部");
dept.setDept_loc("上海");
this.deptService.add(dept);
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 配置直接扫描 -->
<context:component-scan base-package="com.txp.spring.aop.service"/>
<!-- 配置代理类 -->
<bean id="serviceAdvise" class="com.txp.spring.aop.ServiceAdvise"/>
<aop:config>
<aop:pointcut expression="execution(public * com.txp.spring.aop.service..*.*(..))" id="commonPointcut"/>
<aop:aspect ref="serviceAdvise">
<aop:before method="beforeHandle" arg-names="vo" pointcut="execution(public * com.txp.spring.aop.service..*.*(..)) and args(vo)"/>
<aop:after method="afterHandle" pointcut-ref="commonPointcut"/>
<!-- 后置返回通知 -->
<aop:after-returning method="afterReturnHandle" pointcut-ref="commonPointcut" returning="resval" arg-names="resval"/>
<!-- 后置异常通知 -->
<aop:after-throwing method="afterThrowHandle" pointcut-ref="commonPointcut" throwing="e" arg-names="e"/>
<!-- 环绕通知 -->
<aop:around method="aroundHandle" pointcut-ref="commonPointcut"/>
</aop:aspect>
</aop:config>
</beans>
spring对于aop的配置提供了注解支持,也就是可以直接通过注解的形式完成aop的代理操作。
aop注解:
- @Aspect:用于配置代理类
- @Before():前置操作
- @After():后置操作
- @AfterThrowing():后置异常通知
- @AfterReturning():后置返回通知
- @Around():环绕通知
范例:使用注解完成aop代理操作
@Component // 注入bean
@Aspect // aop注解开启
public class ServiceAdviseAnnotation {
@Before(value = "execution(public * com.txp.spring.aop.service..*.*(..)) and args(vo)", argNames = "vo")
public void beforeHandle(Object param) {
System.out.println("业务调用之前param=" + param);
}
@After(value = "execution(public * com.txp.spring.aop.service..*.*(..))")
public void afterHandle() {
System.out.println("业务调用之后");
}
@AfterThrowing(value = "execution(public * com.txp.spring.aop.service..*.*(..))", throwing = "e", argNames = "e")
public void afterThrowHandle(Exception e) {
System.out.println("后置异常返回通知 e=" + e);
}
@AfterReturning(value = "execution(public * com.txp.spring.aop.service..*.*(..))", returning = "resval", argNames = "resval")
public void afterReturnHandle(Object result) {
System.out.println("后置返回通知 result=" + result);
}
@Around(value = "execution(public * com.txp.spring.aop.service..*.*(..))")
public Object aroundHandle(ProceedingJoinPoint point) {
System.out.println("环绕通知:方法执行之前" + Arrays.toString(point.getArgs()));
Object result = null;
try {
result = point.proceed(point.getArgs());
System.out.println("环绕通知:方法执行完毕返回值 result=" + result);
} catch (Throwable e) {
System.out.println("环绕通知:产生异常" + e);
}
System.out.println("环绕通知:全部执行完毕");
System.out.println("--------------------------------------------");
return result;
}
}
// xml文件直接配置注解扫描即可。
<!-- 配置直接扫描 -->
<context:component-scan base-package="com.txp.spring.aop"/>
<!-- aop注解支持 -->
<aop:aspectj-autoproxy/>
JDBC操作模板
目前已经有很多基于ORM设计的数据层开发框架,如轻量级的(Ibatis、Myatis),重量级的(Hirbernate),而spring除了提供各种ORM框架的整合外,也提供了一个各位小巧的数据层工具类,JDBCTemplate。
要想使用JDBCTemplate工具类,需要引入相关开发包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
既然是和数据库打交道,那肯定需要和数据库进行连接,才能操作数据库,spring提供了
org.springframework.jdbc.datasource.DriverManagerDataSource类来配置数据库的连接。
此类提供了如下常用方法:
方法名称 | 描述 |
---|---|
public DriverManagerDataSource(String url, String username, String password) {} | 设置数据库连接地址,用户名,密码 |
public void setDriverClassName(String driverClassName) {} | 设置数据库连接驱动地址 |
public void setUrl(String url) {} | 设置数据库连接地址 |
public void setUsername(String username) {} | 设置数据库用户名 |
public void setUsername(String username) {} | 设置数据库用户名 |
public void setPassword(String password) {} | 设置数据库访问密码 |
范例:连接mysql数据库
public class DatabaseConnection {
// 数据库连接信息
private static final String DBDRIVER = "com.mysql.jdbc.Driver";
private static final String DBURL = "jdbc:mysql://106.75.222.208:3306/spring?useSSL=false&useUnicode=true&characterEncoding=UTF-8";
private static final String USERNAME = "dskj";
private static final String PASSWORD = "Xx";
public static void main(String[] args) throws Exception{
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(DBDRIVER);
dataSource.setUrl(DBURL);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);
// 获取数据库连接信息
Connection conn = dataSource.getConnection();
System.out.println(conn);
conn.close();
}
}
以上通过Sring提供的类可以配置数据库连接,但是在正常的开发环境中,都是基于配置文件来进行数据库连接配置的。
范例:通过配置文件配置数据库连接
## 定义properties,定义数据库连接信息
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://106.75.222.208:3306/spring?useSSL=false&useUnicode=true&characterEncoding=UTF-8
db.username=root
db.password=fl2X_39c
// spring的xml配置文件中注入
<!-- 注入数据库连接spring-datasource.xml -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${db.driver}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
<!-- spring-base.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 引入jdbc配置properties文件 -->
<context:property-placeholder location="classpath:config/*.properties"/>
<!-- 引入spring其他配置文件 -->
<import resource="spring-datasource.xml"/>
</beans>
配置好以上信息后,就可以获取数据库的连接对象。
当可以正常连接数据库,获取数据库连接对象后,就可以视同jdbcTemplate工具类来进行数据库语句的操作。jdbcTemplate提供了如下常用操作方法:
方法 | 描述 |
---|---|
public void setDataSource(dataSource) {} | 设置数据库连接信息 |
public int update(String sql, Object… args){} | 数据更新 |
public int[] batchUpdate(String sql, final BatchPreparedStatementSetter pss){} | 批量更新 |
public List query(String sql, RowMapper rowMapper){} | 查询数据 |
public void execute(final String sql){} | 执行sql |
范例:通过JDBCTemplate完成增删改查操作
使用之前,需要配置jdbcTemplate的数据源。
<!-- 配置数据源 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
// 使用jdbcTemplate完成SQL语句执行
@ContextConfiguration(locations = {"classpath:spring-base.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJDBCTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testAdd() {
String sql = "INSERT INTO news(title, note, pubdate, price, readcount) VALUES (?, ?, ?, ?, ?)";
String title = "年假通知";
String note = "哈哈哈哈";
Date data = new Date();
Double price = 10000.99;
int readcount = 500;
int len = this.jdbcTemplate.update(sql, title, note, data, price, readcount);
System.out.println(len);
}
// 获取自动增长id
@Test
public void testAddgetKey() {
KeyHolder keyHolder = new GeneratedKeyHolder();
String sql = "INSERT INTO news(title, note, pubdate, price, readcount) VALUES (?, ?, ?, ?, ?)";
String title = "测试名称名称";
String note = "哈哈哈哈";
Date date = new Date();
Double price = 10000.99;
int readcount = 500;
int len = this.jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement pstmt = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, title);
pstmt.setString(2, note);
pstmt.setDate(3, new java.sql.Date(date.getTime()));;
pstmt.setDouble(4, price);
pstmt.setInt(5, readcount);
return pstmt;
}
}, keyHolder);
System.out.println("主键:" + keyHolder.getKey() + "\t" + "更新行数:" + len);
}
@Test
public void testUpdate() {
String sql = "UPDATE news SET title=? WHERE nid=?";
String title = "珂珂珂珂";
Long nid = 5L;
int len = this.jdbcTemplate.update(sql, title, nid);
System.out.println("更新行数:" + len);
}
@Test
public void testDelete() {
String sql = "DELETE FROM news WHERE nid=?";
Long nid = 6L;
int len = this.jdbcTemplate.update(sql, nid);
System.out.println("更新行数" + len);
}
@Test
public void getNews() {
String sql = "SELECT nid, title, note, pubdate, readcount FROM news";
List<News> newsList = this.jdbcTemplate.query(sql, new RowMapper<News>() {
@Override
public News mapRow(ResultSet rs, int rowNum) throws SQLException {
News vo = new News();
vo.setId(rs.getLong(1));
vo.setTitle(rs.getString(2));
vo.setNote(rs.getString(3));
vo.setPubdate(rs.getDate(4));
vo.setReadcount(rs.getInt(5));
return vo;
}});
System.out.println(newsList);
}
@Test
public void getNewsPage() {
String column = "title";
String keyword = "";
int currentPage = 1;
int lineSize = 10;
String sql = "SELECT nid, title, note, pubdate, readcount FROM news WHERE " + column + " LIKE ? LIMIT ?,?";
List<News> newsList = this.jdbcTemplate.query(sql, new Object[] {"%"+keyword+"%", (currentPage - 1) * lineSize, lineSize}, new RowMapper<News>() {
@Override
public News mapRow(ResultSet rs, int rowNum) throws SQLException {
News vo = new News();
vo.setId(rs.getLong(1));
vo.setTitle(rs.getString(2));
vo.setNote(rs.getString(3));
vo.setPubdate(rs.getDate(4));
vo.setReadcount(rs.getInt(5));
return vo;
}});
System.out.println(newsList);
}
}
C3P0数据库连接池使用
c3p0是一个出现比较久的数据库连接池工具。要想使用c3p0工具,需要引入如下开发包依赖:
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
提供了一个数据库连接配置类:com.mchange.v2.c3p0.ComboPooledDataSource
次类可以配置如下连接信息:
属性 | 描述 |
---|---|
driverClass | 数据库连接驱动地址 |
jdbcUrl | 数据库连接地址 |
user | 数据库用户名 |
password | 用户密码 |
maxPoolSize | 连接池最大连接数量 |
minPoolSize | 连接池最小连接数量 |
initialPoolSize | 初始化线程池连接数量 |
maxIdleTime | 连接池满载后最长等待时间(毫秒为单位) |
范例:使用c3p0管理数据库连接
在配置数据库信息配置文件中修改为如下配置:
# 数据库驱动连接地址
db.driverClass=com.mysql.jdbc.Driver
# 数据库连接地址
db.jdbcUrl=jdbc:mysql://106.75.222.208:3306/spring?useSSL=false&useUnicode=true&characterEncoding=UTF-8
# 用户名
db.user=root
# 密码
db.password=fl2X_39c
# 最大线程池连接数量
db.maxPoolSize=1
# 最小线程池连接数量
db.minPoolSize=1
# 初始化线程池连接数量
db.initialPoolSize=1
# 连接最长等待时间
db.maxIdleTime=2000
// 使用c3p0数据库连接池
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${db.driverClass}"/>
<property name="jdbcUrl" value="${db.jdbcUrl}"/>
<property name="user" value="${db.user}"/>
<property name="password" value="${db.password}"/>
<property name="maxPoolSize" value="${db.maxPoolSize}"/>
<property name="minPoolSize" value="${db.minPoolSize}"/>
<property name="initialPoolSize" value="${db.initialPoolSize}"/>
<property name="maxIdleTime" value="${db.maxIdleTime}"/>
</bean>
// 测试
public class TestDemo {
public static void main(String[] args) {
ApplicationContext act = new ClassPathXmlApplicationContext("spring-datasource.xml");
DataSource dataSource = act.getBean("dataSource", DataSource.class);
System.out.println(dataSource);
}
}
事务处理(补充事务知识)
事务控制
什么是事务?事务简单点理解,就是确定我们对数据库中数据的修改是否保留。来确保最终存储数据的一致性。
事务处理遵循四个原则
事务是对数据库的操作,我们平时只要牵扯到数据的增加、修改、删除,就需要对其进行事务提交。那么,事务的处理就需要遵循如下四个原则:
-
原子性:在进行事务提交时,所有的操作要么全部完成,要么全部失败,不能说我们执行了数据的增加,随后又对该数据进行了修改,最后提交事务,不能说增加成功了,修改失败了。
-
一致性:一致性就是我们操作的数据要保持一致,一致性和原子性有着紧密的联系,像玩游戏可以赠送礼品一样,A和B两个人,A人把自己游戏仓库里的 “宝刀” 送给了B人,那么此时应该是A人的仓库里面要减少一把 “宝刀”,而B人的仓库里面增加一把宝刀。不能说减少了A人的游戏装备成功后,正要增加到B的仓库装备,此时发生了错误,导致B人没有收到赠礼,而A人却白白丢失了一把刀。
-
隔离性:多个用户访问数据库时,各自的事务处理之间互不影响,而事务的隔离有四种隔离级别,每个隔离级别都有不同的属性。
-
持久性:一旦进行了事务的提交,则不能回退回事务之前的数据。
并发下的数据库访问问题及解决方案?
为什么需要事务?在一套系统的使用中,肯定是大量的用户同时使用,并发操作访问数据库的情况下,可能会造成数据更新丢失、脏读、不可重复读、幻读问题。
如何解决这个问题?我们在每个人进行数据操作时,做一些规则限定(事务),我们知道事务处理需要遵循四个基本原则,通过事务的隔离性来将每一个不同的session会话隔离开来。而事务的隔离有四种隔离级别,每个隔离级别都有不同的属性。
数据更新丢失问题及解决
上图所示,小美和小明在操作同一数据时,导致更新冲突,数据显示不正确。这个时候就有一个锁机制,当小美在进行数据写入时,对该数据进行加锁(排他锁 Exclusive Lock),此时,只要该锁没有被释放(也就是没有提交事务,执行COMMIT),小明无法对该数据进行写入操作。如下图:
以上操作,就是READ UNCOMMITTED隔离级别,此隔离级别就是在执行数据修改时加入排他锁(Exclusive Lock),执行完毕后释放该锁,其他session会话才可以进行该数据的修改,此时就避免了数据更新丢失问题。但是,此时会引发一个新的问题:脏读。
脏读
可以发现,READ UNCOMMITTED 隔离级别可以让其他的session会话读取到没有被提交的数据,从而导致小明读取到了错误的数据。
如何解决脏读?
写入数据时,通过排他锁(Exclusive Lock)可以解决数据更新丢失问题,那是不是也可以在读取数据时加入锁,如果在读数据时也使用排他锁,太占用数据库资源,此时,就有一个新的约定,共享锁(Share Lock),该锁在数据读取完成后就立马被释放。
此时就避免了脏读的发生,这样的处理操作在数据库中为第二隔离级别:READ COMMITTED隔离级别,避免了其他的session可以读取到没有被提交事务的数据。
通过第二隔离级别READ COMMITTED在读数据时,加入共享锁(Share Lock),读取完成后立马释放掉该锁,写数据时和第一隔离级别一样,很好的解决脏读问题,但是又冒出了一个新的问题:不可重复读。
什么是不可重复读?
此时,不可重复读的原因就是小美读取完数据后立马释放掉读锁导致,解决这样的问题,还是需要在读数据时一直锁住,直到释放。此时就有了第三个事务隔离级别REPEATABLE READ ,可以来解决掉不可重复读、脏读、数据更新丢失问题。
如何解决不可重复读?
REPEATABLE READ事务隔离级别在MySQL数据库中为默认的隔离级别,可以解决之前遇到的所有问题。但是此时又有一种不一样的情况,幻读。
幻读?
幻读就是当一个人去修改表中所有的数据,之后另外一个session去新增一条数据,之后修改的人去看自己修改的数据时,发现有没有修改成功的,这样的数据就叫做幻读。
要想解决幻读问题,就要使用事务隔离级别中的大招Serializable(串行化),所有的session操作,都需要等待上一个执行完成后,才可以进行数据操作,这样的隔离级别是最高的,同样,也是最消耗服务器性能的隔离级别,在实际开发中,不推荐使用此事务隔离级别。
总结
事务处理遵循四个原则:原子性、一致性、隔离性、持久性
并发访问数据库会造成:数据更新丢失、脏读、不可重复读、幻读问题
事务四个隔离级别:
READ UNCOMMITTED:写数据时加入排他锁(Exclusive Lock)
READ COMMITTED:写数据时加入排他锁(Exclusive Lock),读数据时加入共享锁(Share Lock),读完数据后立马释放掉读锁。
REPEATABLE READ:写数据时加入排他锁(Exclusive Lock)读数据时加入共享锁(Share Lock),读完后不会立马释放掉读锁。
SERIALIZABLE:串行化,需要等待上一个事务处理完成后,才执行,所牺牲代价最高
不同隔离级别所解决问题?
隔离级别 | 数据的更新丢失 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
READ UNCOMMITTED | Y | N | N | N |
READ COMMITTED | Y | Y | N | N |
REPEATABLE READ | Y | Y | Y | N |
SERIALIZABLE | Y | Y | Y | Y |
Spring事务控制
只要是项目,都是基于数据库进行开发的,对于数据库而言,为保证数据的一致性,对事务处理提出了四大基本原则:即原子性、一致性、隔离性、持久性。并发操作同一数据时,通过不同的隔离级别来保证多个用户之间的操作的数据不会相互影响。避免脏读、不可重复的、幻读问题,最后通过事务提交来确保最终所操作数据的一致性。
要想使用Spring事务控制,需要引入相关依赖包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.3</version>
</dependency>
Spring对事务的提交也提供了支持,其核心接口:public interface PlatformTransactionManager
该接口提供了事务处理方法:
方法 | 描述 |
---|---|
TransactionStatus getTransaction(TransactionDefinition definition) | 获取当前的事务状态 |
void commit(TransactionStatus status) | 提交事务 |
void rollback(TransactionStatus status) | 回滚数据 |
根据该接口提供的方法描述,要想进行事务的提交,我们需要使用TransactionDefinition接口来指定具体的事务传播属性,并通过**getTransaction()**方法获取事务状态,才可以进行事务的提交和回滚操作。
事务的传播属性
事务的传播描述的是,当前所执行的事务处理是否继续沿用到其他方法上。
TransactionDefinition定义了如下7个属性:
属性名称 | 描述 |
---|---|
PROPAGATION_MANDATORY | 支持当前事务,如果当前没有事务运行,则抛出异常 |
PROPAGATION_NESTED | 如果当前有事务运行则嵌套在当前事务中运行,如果没有则以PROPAGATION_REQUIRED |
PROPAGATION_NEVER | 以非事务的方式运行,如果当前有事务,则抛出异常 |
PROPAGATION_NOT_SUPPORTED | 不执行当前事务,以非事务的方式来运行 |
PROPAGATION_REQUIRED | 执行当前事务处理,如果当前没有事务,则创建一个新的事务处理 |
PROPAGATION_REQUIRES_NEW | 总是创建一个新的事务来运行,如果当前存在是否,则挂起当前事务 |
PROPAGATION_SUPPORTS | 执行当前事务,如果当前没有事务运行则执行非事务处理 |
范例:spring事务处理,这里使用到的数据层开发组件是Spring提供JdbcTemplate
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class TestJdbcTemplate {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private PlatformTransactionManager platformTransactionManager;
@Test
public void testInsert() {
// TransactionDefinition的子类DefaultTransactionDefinition,用于设置具体使用的传播属性
DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
// 设置传播属性
defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 设置数据库隔离级别,此处使用默认的隔离级别
defaultTransactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
// 获取事务状态
TransactionStatus transactionStatus = this.platformTransactionManager.getTransaction(defaultTransactionDefinition);
try {
String sql = "INSERT INTO user_info VALUES(?, ?, ?)";
this.jdbcTemplate.update(sql, null, "谭旭鹏", 20);
// 提交事务
this.platformTransactionManager.commit(transactionStatus);
}catch (Exception e){
// 回滚事务
this.platformTransactionManager.rollback(transactionStatus);
}
}
}
理解事务传播属性?
在原生的Java开发中,对数据库的交互支持只有JDBC,其提供的Connection接口有如下的事务操作方法:
方法 | 描述 |
---|---|
void commit() | 提交事务 |
void rollback() | 回滚事务 |
void setAutoCommit(boolean autoCommit) | 设置事务是否自动提交:false取消、true自动提交 |
在正常的开发中,都是基于分层的模式开发,即将业务与数据分离,而我们一个业务当中可能会去操作多个数据,比如我们要实现一个积分兑换功能,那么该功能会有如下的数据动作:
- 查询该用户积分是否满足所兑换的商品
- 扣除用户的积分
- 为该用户增加所兑换的物品
所以我们的事务处理放在业务层最为合适,当我们成功扣除用户积分时,就在为该用户增加所兑换物品,如果此时发生了错误,则回滚。那这个用户的积分也不会受到影响。如下图:
但是,还有一种业务层调用业务层的情况,如下操作:
jdbc事务演示
根据上面视频可以发现,修改完数据回到增加方法时,发生了错误,在一个业务中,增加没有成功,而修改却成功了。这样最终操作的结果就是错误的。
范例:使用Spring事务控制进行事务处理
Spring事务控制演示
使用注解配置Spring事务控制
在实际的开发中,肯定不会像上面的代码一样,手动实例化子类,手动进行事务的提交和回滚操作,我们可以通过注解来配置Spring事务控制,由Spring自动帮助我们完成事务的提交操作。在Spring配置文件中加入如下配置:
开启Spring事务注解:
<tx:annotation-driven transaction-manager=“transactionManager”/>
使用 @Transactional 设置事务传播属性
// 业务层接口操作标准
@Service
public interface IUserInfoService {
@Transactional(propagation = Propagation.REQUIRED)
int addUserInfo();
}
// 业务层实现子类
@Service
public class UserInfoServiceImpl implements IUserInfoService {
@Autowired
private IUserInfoDAO userInfoDAO;
@Override
public int addUserInfo() {
return this.userInfoDAO.doCreateUserInfo();
}
}
// 数据层接口
public interface IUserInfoDAO {
int doCreateUserInfo();
}
// 数据层实现子类
@Repository
public class UserInfoDAOImpl implements IUserInfoDAO{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int doCreateUserInfo() {
String sql = "INSERT INTO user_info VALUES(?, ?, ?)";
return this.jdbcTemplate.update(sql, null, "老铁", 18);
}
}
使用AOP代理事务处理
要想使用aop动态代理帮助我们完成事务自动处理,除了需要引入事务依赖外,还需要引入aop依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.3</version>
</dependency>
在spring配置文件中,配置要代理事务处理的方法
<!-- 事务处理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--
配置事务:
是add、edit、delete开头的方法,都是用REQUIRED事务属性
是get开头的方法,则以只读运行
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" isolation="DEFAULT" propagation="REQUIRED"/>
<tx:method name="edit*" isolation="DEFAULT" propagation="REQUIRED"/>
<tx:method name="delete*" isolation="DEFAULT" propagation="REQUIRED"/>
<tx:method name="get*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 通过aop配置代理 -->
<aop:config>
<aop:pointcut expression="execution(public * com.txp.service..*.*(..))" id="txPointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>