本文是在看了b站视频【尚硅谷Spring框架视频教程(spring5源码级讲解)】后,手动实现及理解整理
视频地址:https://www.bilibili.com/video/BV1Vf4y127N5?spm_id_from=333.999.0.0
Spring是轻量级开源JavaEE框架,可以解决企业应用开发的困难性。
有两个核心部分:
Spring 特点:
public class Book {
private String bName;
private String bAuthor;
public void testDemo() {
System.out.println(bName + "::" + bAuthor);
}
}
3.创建bean1.xml文件
<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="book" class="cn.lych4.spring5.bean.Book">bean>
beans>
4.创建对象
//1. 加载spring 配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//2.获取配置的对象
Book book = context.getBean("book", Book.class);
System.out.println(book);
book.testDemo();
控制反转(IOC), 就算把对象创建和对象之间的调用过程交给Spring容器进行管理
IOC的目的是为了降低耦合
第一步: xml配置文件,配置创建的对象
<bean id="userDao" class="cn.lych4.dao.impl.UserDaoImpl">bean>
第二步: 有service 类 和 dao 类, 创建工厂类。进一步降低耦合度
class UserFactory{
public static UserDao getDao() {
String classValue = class属性值; // 1. XML解析
Class clazz = Class.forName(classValue) // 通过反射创建对象
return (UserDao)clazz.newInstance();
}
}
IOC 思想基于IOC容器完成,IOC容器底层就算对象工厂
Spring 提供两种方式实现 IOC 容器: (两个接口)
Bean管理指的是两个操作:
①. 基于XML配置文件方式实现, ②.基于注解方式实现
在spring配置文件中,使用bean标签,标签里面添加对应属性,就可以是实现对象创建
*id属性: 对象标识
*class属性: 类全路径
name属性:早期属性,和id类似,只不过可以加特殊符号
默认执行无参构造完成对象创建
对象类必须有对应set方法
配置文件:
<bean id="book" class="cn.lych4.spring5.bean.Book">
<property name="bName" value="易筋经">property>
<property name="bAuthor" value="达摩老祖">property>
bean>
对象类必须有对应有参构造方法
配置文件:
<bean id="orders" class="cn.lych4.spring5.bean.Orders">
<constructor-arg name="oname" value="电脑">constructor-arg>
<constructor-arg name="address" value="China">constructor-arg>
bean>
1.字面量
null 空值
<property name="bAuthor">
<null/>
property>
属性值包括特殊符号
特殊符号转义,或者是把特殊内容写到
<property name="bAuthor">
<value>]]>value>
property>
2.注入属性-外部bean
创建一个Service类和Dao类
在Service类中调用Dao类中的方法
在spring配置文件中进行配置
<bean id="userService" class="cn.lych4.spring5.service.UserService">
<property name="userDao" ref="userDaoImpl">property>
bean>
<bean id="userDaoImpl" class="cn.lych4.spring5.dao.UserDaoImpl">bean>
3.注入属性-内部bean
(1)一对多关系:部门和员工
(2)在实体类中表示一对多的关系
//部门类,一
public class Dept {
private String name;
public void setName(String name) {
this.name = name;
}
}
//员工类,多
public class Emp {
private String ename;
private String gender;
//员工属于某一个部分
private Dept dept;
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setDept(Dept dept) {
this.dept = dept;
}
}
(3)在spring配置文件中进行配置
<bean id="emp" class="cn.lych4.spring5.bean.Emp">
<property name="ename" value="lucy">property>
<property name="gender" value="女">property>
<property name="dept">
<bean id="dept" class="cn.lych4.spring5.bean.Dept">
<property name="name" value="安保部">property>
bean>
property>
bean>
3.注入属性-级联赋值
(1) 第一种
<bean id="emp" class="cn.lych4.spring5.bean.Emp">
<property name="ename" value="lucy">property>
<property name="gender" value="女">property>
<property name="dept" ref="dept">property>
bean>
<bean id="dept" class="cn.lych4.spring5.bean.Dept">
<property name="dname" value="财务部">property>
bean>
(2)第二种
<bean id="emp" class="cn.lych4.spring5.bean.Emp">
<property name="ename" value="lucy">property>
<property name="gender" value="女">property>
<property name="dept" ref="dept">property>
<property name="dept.dname" value="技术部">property>
bean>
<bean id="dept" class="cn.lych4.spring5.bean.Dept">
<property name="dname" value="财务部">property>
bean>
注入数组、List、Map
<bean id="stu" class="cn.lych4.spring5.collectiontype.Stu">
<property name="courses">
<array>
<value>java课程value>
<value>数据库课程value>
array>
property>
<property name="list">
<list>
<value>张三value>
<value>三哥value>
list>
property>
<property name="maps">
<map>
<entry key="爱好" value="学习">entry>
map>
property>
<property name="sets">
<set>
<value>Javavalue>
<value>MySQLvalue>
set>
property>
bean>
设置对象集合
<bean id="stu" class="cn.lych4.spring5.collectiontype.Stu">
<property name="courseList">
<list>
<ref bean="course1">ref>
<ref bean="course2">ref>
<ref bean="course3">ref>
list>
property>
bean>
<bean id="course1" class="cn.lych4.spring5.collectiontype.Course" >
<property name="cname" value="Java">property>
bean>
<bean id="course2" class="cn.lych4.spring5.collectiontype.Course" >
<property name="cname" value="Python">property>
bean>
<bean id="course3" class="cn.lych4.spring5.collectiontype.Course" >
<property name="cname" value="Go">property>
bean>
抽取集合值, 引入util命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:list id="bookList" >
<value>MySQL必知必会value>
<value>JVM调优value>
util:list>
<bean id="book" class="cn.lych4.spring5.collectiontype.Book" >
<property name="bnames" ref="bookList">property>
bean>
beans>
Spring有两种类型的bean,一种是普通的bean 另外一种是工厂bean (FactoryBean)
创建一个Bean,实现FactoryBean接口
public class MyBean implements FactoryBean<Course> {
//定义返回bean
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("abc");
return course;
}
public Class<?> getObjectType() {
return null;
}
public boolean isSingleton() {
return false;
}
}
xml配置文件
<bean id="myBean" class="cn.lych4.spring5.factorybean.MyBean">bean>
测试:
@Test
public void testFactoryBean() {
ApplicationContext context = new ClassPathXmlApplicationContext("factorybean.xml");
// MyBean myBean = context.getBean("myBean", MyBean.class);
Course myBean = context.getBean("myBean", Course.class);
System.out.println(myBean);
}
在Spring里面可以设置创建的bean实例是单实例还是多实例
在Spring里面默认情况下创建的bean都是单实例bean
scope属性可以设置单实例还是多实例。scope属性值: singleton、 prototype
<bean id="book" class="cn.lych4.spring5.collectiontype.Book" scope="prototype">bean>
scope值是 singleton 的时候,加载spring配置文件就会创建实例对象
scope值是 prototype 时候, 不是在加载 spring 配置文件时候创建对象,获取对象的时候才会创建
①通过构造器创建bean实例(无参构造)
②为bean的属性设置值和对其他bean引用(调用set方法)
③把bean实例传递bean后置处理器的Before方法(需要配置)
③调用bean的初始化的方法(需要进行配置初始化的方法)
③把bean实例传递bean后置处理器的After方法 (需要配置)
④bean可以使用了(对象获取到了)
⑤当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)
<bean id="myBeanPost" class="cn.lych4.spring5.bean.MyBeanPost">bean>
bean标签属性autowire,配置自动装配。
两个属性常用值:
① 先直接配置数据库信息, 配置德鲁伊连接池
记得引入jar包
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver">property>
<property name="url" value="jdbc:mysql://localhost:3306/userDb">property>
<property name="username" value="root">property>
<property name="password" value="root">property>
bean>
② 引入外部属性文件
创建外部属性文件
引入context名称空间,把properties属性文件引入到 spring 配置文件中
<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:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}"/>
<property name="url" value="${prop.url}"/>
<property name="password" value="${prop.password}"/>
bean>
beans>
注解可以用在类、方法、属性上面
@Component
@Service
@Controller
@Repository
这四个注解功能一样,都可以用来创建bean实例
1.需要额外引入AOP的jar包
2.开启组件扫描
<context:component-scan base-package="cn.lych4.spring5"/>
3.创建类,在类上添加创建对象注解
@Component(value = "userService") //效果和 一样
public class UserService {
public void add() {
System.out.println("service::add");
}
}
常用包扫描设置:
<context:component-scan base-package="cn.lych4.spring5" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
context:component-scan>
<context:component-scan base-package="cn.lych4.spring5">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
context:component-scan>
@Autowired : 根据属性类型进行自动装配
@Qualifier: 根据属性名称进行注入
@Resource: 可以根据类型注入,也可以根据名称注入
@Value : 注入普通类型属性
Qualifier 注解要和Autowired 一起使用, 因为按类型注入一个接口可能有多个实现类,对应多个对象,按类型的话就找不到。
@Autowired
@Qualifier(value = "userDao1")
private UserDao userDao;
Resource 注解 既可以根据类型直接注入,也可以加个name来指定按名称找
Value 注解
@Value(value = "abc")
private String name;
包扫描配置都不要了
@Test
public void testService2() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
面向切面编程(AOP), 利用AOP可以对业务进行隔离,降低业务各部分之间的耦合度,提高代码重用性。提高开发效率
通过不修改源码的方式,在主干功能里面添加新的功能
两种情况 的动态代理
①有接口的情况, 使用JDK的动态代理
一个接口,一个实现类
JDK动态代理, 创建一个UserDao 接口的实现类代理对象,去增强类的方法
②没有接口的情况,使用CGLIB动态代理
创建当前类的子类的代理对象
使用JDK动态代理,使用Proxy类里面的方法创建代理对象
三个参数分别是 类加载器、这个类实现的接口、实现InvocationHandler接口,写增强部分
代码:
public class JDKProxy {
public static void main(String[] args) {
//创建接口实现类代理对象
Class[] interfaces = {UserDao.class};
UserDaoImpl userDao = new UserDaoImpl();
UserDao proxyUserDao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = proxyUserDao.add(1, 2);
System.out.println(result);
}
}
class UserDaoProxy implements InvocationHandler {
private Object obj;
//1. 创建的是谁的代理对象,把这个人传进来
public UserDaoProxy(Object obj) {
this.obj = obj;
}
//增强逻辑
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前, 这里还可以根据方法名实现只增强接口中的某些方法
System.out.println("方法执行之前...方法名:" + method.getName() + "...传递的参数:" + Arrays.toString(args)) ;
//执行原方法
Object ret = method.invoke(obj, args);
//方法之后
System.out.println("方法之后执行..." + obj);
return ret;
}
}
UserDao
public interface UserDao {
public int add(int a, int b);
}
UserDaoImpl
public class UserDaoImpl implements UserDao {
public int add(int a, int b) {
System.out.println("add方法执行了");
return a + b;
}
}
1. 连接点: 类中哪些方法可以被增强,这些方法就称为连接点
2. 切入点: 实际被真正增强的方法叫做切除点
3. 通知(增强):实际增强的逻辑部分称为通知。有前置通知、后置通知、环绕通知、异常通知、最终通知
4. 切面 :是动作, 把通知应用到切入点的过程就是切面
Spring 框架中一般基于AspectJ实现AOP操作。
AspectJ是独立的AOP框架, 一般把AspectJ和Spring框架一起使用,进行AOP操作
基于AspectJ 实现AOP操作,两种方式: xml配置方式和注解方式。
2.切入点表达式
切入点表达式作用: 知道对哪个类里面的哪个方法进行增强
语法结构:
//举例1: 对cn.lych4.spring5.dao.BookDao 类里面的add()进行增强
execution(* cn.lych4.spring5.dao.BookDao.add(..))
//举例2: 对cn.lych4.spring5.dao.BookDao 类里面所有方法进行增强
execution(* cn.lych4.spring5.dao.BookDao.*(..))
//举例3: 对cn.lych4.spring5.dao 包里所有类里面所有方法进行增强
execution(* cn.lych4.spring5.dao.*.*(..))
1.创建类,在类中定义方法
@Component
public class User {
public void add() {
System.out.println("add........");
}
}
2.创建增强类,编写增强逻辑,不同方法代表不同通知
@Component
@Aspect
public class UserProxy {
//前置通知
@Before(value = "execution(* cn.lych4.spring5.aopanno.User.add(..))")
public void before() {
System.out.println("before.....");
}
// 最终通知, 不管有没有异常都通知
@After(value = "execution(* cn.lych4.spring5.aopanno.User.add(..))")
public void after() {
System.out.println("after.....");
}
//返回通知, 返回值执行完才通知 //后置通知
@AfterReturning(value = "execution(* cn.lych4.spring5.aopanno.User.add(..))")
public void afterReturning() {
System.out.println("afterReturning.....");
}
//异常通知
@AfterThrowing(value = "execution(* cn.lych4.spring5.aopanno.User.add(..))")
public void afterThrowing() {
System.out.println("afterThrowing.....");
}
//环绕通知, 方法前后都执行
@Around(value = "execution(* cn.lych4.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("around.....环绕之前....");
proceedingJoinPoint.proceed();
System.out.println("around.....环绕之后.....");
}
}
3.进行通知配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="cn.lych4.spring5"/>
<aop:aspectj-autoproxy/>
beans>
4.测试:
@Test
public void testAopAnno(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("user", User.class);
user.add();
}
5.可以进行公共切入点抽取
//相同切入点抽取
@Pointcut(value = "execution(* cn.lych4.spring5.aopanno.User.add(..))")
public void pointDemo() {
}
//前置通知
@Before(value = "pointDemo()")
public void before() {
System.out.println("before.....");
}
6.如果有多个增强类对同一方法进行增强,可以设置增强优先级
给增强类添加一个@Order() 注解, 括号里填入数字,数字越小优先级越高
@Component
@Aspect
@Order(3)
public class PersonProxy {
//前置通知
@Before(value = "execution(* cn.lych4.spring5.aopanno.User.add(..))")
public void before() {
System.out.println("PersonProxy before.....");
}
}
这样配置就欧克了
<bean id="user" class="cn.lych4.spring5.aopanno.User"/>
<bean id="userProxy" class="cn.lych4.spring5.aopanno.UserProxy"/>
<aop:config>
<aop:pointcut id="p" expression="execution(* cn.lych4.spring5.aopanno.User.add(..))"/>
<aop:aspect ref="userProxy">
<aop:before method="before" pointcut-ref="p"/>
aop:aspect>
aop:config>
创建配置类,加三个注解
@Configuration //表明这是配置类
@ComponentScan(basePackages = {"cn.lych4.spring5"}) //开启组件扫描
@EnableAspectJAutoProxy(proxyTargetClass = true) //开启Aspect生成代理对象
重点
AOP概念, 底层原理动态代理(有接口、无接口), AOP术语 , AspectJ注解实现AOP操作
JdbcTemplate是Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库的操作
1.新引入jar包
2.配置数据库连接池,创建JdbcTemplate对象
jdbc.properties:
jdbc.driverClass=com.alibaba.druid.pool.DruidDataSource
jdbc.url=jdbc:mysql:// /user_db
jdbc.username=root
jdbc.password=
bean1.xml:
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
bean>
3.创建Service类、Dao类,在Dao类中注入jdbcTemplate对象
BookService
@Service
public class BookService {
//注入Dao
@Autowired
private BookDao bookDao;
public void add(Book book){
bookDao.add(book);
}
}
BookDao
public interface BookDao {
public void add(Book book);
}
BookDaoImpl
@Repository
public class BookDaoImpl implements BookDao{
//注入jdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
public void add(Book book) {
//1.创建sql语句
String sql = "insert into book values(?,?,?)";
//2. 调用方法实现
Object[] args = { book.getBookId(), book.getBookName(), book.getBstatus()};
int update = jdbcTemplate.update(sql,args);
System.out.println(update);
}
}
5.创建数据库表对应的实体类, 创建数据库表
public class Book {
private String bookId;
private String bookName;
private String bstatus;
// +getAndSet方法,toString方法
}
注:为了规范,数据库列名最好是下划线分割命名,而不是大驼峰命名
6.测试
@Test
public void testJdbcTemplate() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
Book book = new Book();
book.setBookId("1");
book.setBookName("Java");
book.setBstatus("售出");
bookService.add(book);
}
报错: 严重: {dataSource-1} init error
解决方案: ①检查sql语句 ②MySQL数据库版本和驱动不匹配,改配置文件中的 jdbc.driverClass=com.mysql.jdbc.Driver
修改、删除和添加基本一样, 主要sql语句不一样
修改:
public void updata(Book book) {
String sql = "update book set book_name=?,bstatus=? where book_id=?";
Object[] args = {book.getBookName(), book.getBstatus(), book.getBookId()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
删除
public void delete(String id) {
String sql = "delete from book where book_id=?";
int update = jdbcTemplate.update(sql, id);
System.out.println(update);
}
自行添加接口方法,Service方法, 自行进行修改删除测试, 很简单
查询返回某个值
public Integer selectCount() {
String sql = "select count(*) from book";
//queryForObject(String sql, Class requiredType) 第一个参数sql语句, 第二个参数返回类型class 即你查询的结果返回的类型
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
查询返回对象
public Book findOne(String id) {
String sql = "select * from book where book_id=?";
//queryForObject(String sql, RowMapper rowMapper, Object... args)
// 第一个参数sql语句, 第二个参数RowMapper是接口,返回不同类型的数据,使用这个接口里的实现类,完成查询到数据的封装
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
return book;
}
查询返回集合
public List<Book> findAllBook() {
String sql = "select * from book";
//query(String sql, RowMapper rowMapper) 第一个参数sql语句, 第二个参数RowMapper接口
List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return bookList;
}
可能报错:
org.springframework.dao.IncorrectResultSizeDataAccessException:
Incorrect result size: expected 1, actual 2
这个错误的意思是要求返回一个对象,你却返回两个对象,一看数据库,查询的id有两条数据(实际业务id一般是主键不可能重复,这里没设置)
解决方案,手动删除id重复的内容
批量添加及测试
public void batchAddBook(List<Object[]> batchArgs) {
String sql = "insert into book values(?,?,?)";
//批量添加过程就是把batchArgs遍历,每个进行添加
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
@Test
public void testBatch() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3", "python2", "未售出2"};
Object[] o2 = {"4", "redis2", "已售出2"};
Object[] o3 = {"5", "linux2", "未售出2"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
bookService.batchAdd(batchArgs);
}
批量修改及测试
@Override
public void batchUpdate(List<Object[]> batchArgs) {
String sql = "update book set book_name=?, bstatus=? where book_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
@Test
public void testBatch() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"python2", "未售出2", "3"};
Object[] o2 = {"redis2", "已售出2", "4"};
Object[] o3 = {"linux2", "未售出2", "5"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
bookService.batchUpdate(batchArgs);
}
批量删除
@Override
public void batchDelete(List<Object[]> batchArgs) {
String sql = "delete from book where book_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
@Test
public void testBatch() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
BookService bookService = context.getBean("bookService", BookService.class);
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3"};
Object[] o2 = {"4"};
Object[] o3 = {"5"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
bookService.batchDelete(batchArgs);
}
事务是数据库操作最基本单元,一组操作要么都成功,要么都失败
事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态转变为另一种一致性状态。
事务的ACID特性: 原子性、一致性、隔离性、持久性
举例: 转账业务
建表 t_account
创建实体类
public class User {
private String id;
private String username;
private Integer money;
// +getAndSet方法 +toString方法
}
创建Dao接口、DaoImpl类、 Service类、测试类,注入属性
public interface UserDao {
void addMoney(int money, String username);
void reduceMoney(int money, String username);
}
@Repository
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 多钱
* @param money
*/
@Override
public void addMoney(int money, String username) {
String sql = "update t_account set money=money+? where username=?";
jdbcTemplate.update(sql, money, username);
}
/**
* 少钱
* @param money
*/
@Override
public void reduceMoney(int money, String username) {
String sql = "update t_account set money=money-? where username=?";
jdbcTemplate.update(sql, money, username);
}
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void accountMoney(int money, String from, String to) {
//少钱
userDao.reduceMoney(money, from);
//多钱
userDao.addMoney(money, to);
}
}
@Test
public void testAccount() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney(100, "lucy", "mary");
}
正常情况这样执行业务是没有问题的,但是万一少钱执行完了,突然断电了,多钱方法没执行。这样就不行了。所有需要数据库事务,多钱和少钱是一个整体,要么都成功,要么都不执行进行回滚。
一般是把事务添加到Service层
Spring进行事务操作有两种方式:编程式事务管理和声明式事务管理
声明式事务管理: 注解方式 或基于xml配置文件方式
Spring进行声明式事务管理,底层使用的AOP原理
Spring 提供一个接口代表事务管理器,这个接口针对不同的框架提供不同的实现类
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
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
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">
<context:component-scan base-package="cn.lych4.spring5"/>
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
bean>
<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"/>
beans>
2.在Service类(或者方法)上面添加事务注解
@Transactional 注解添加到类上面就是类中所有方法都加上了事务,加方法上就是方法添加事务。
UserService 模拟异常,看看添加事务注解和不加注解数据库数据的区别。
【加了事务注解,程序出现异常事务会回滚,数据库数据不变。不加事务数据库钱少了】
@Transactional
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void accountMoney(int money, String from, String to) {
//少钱
userDao.reduceMoney(money, from);
int i = 1/0; //模拟异常
//多钱
userDao.addMoney(money, to);
}
}
propagation: 事务传播行为
多事务方法之间进行调用,这个过程中事务是如何进行管理的, 默认是REQUIRED
ioslation: 事务隔离级别
事务具有隔离性,多事务操作之间不会产生影响。不考虑隔离性会产生很多问题:
脏读:事务A读取到了事务B未提交的数据,然后事务B回滚了,事务A拿到错误数据
不可重复读:事务A读取到事务B已提交的修改数据,事务A中前后两次读这个数据不一样
幻读(虚读):事务A读取到事务B已提交的添加的数据,事务A之前读没看到这数据,然后又读看到了这数据
timeout:超时时间
事务在一定时间内必须提交,如果一直没有提交就会回滚。默认值是-1,不会超时
readOnly:是否只读
readOnly默认值是false,表示可以查询也可以增删改。 true的话就只能进行查询操作
rollbackFor:回滚
设置出现哪些异常进行事务回滚
noRollback:不回滚
设置出现哪些异常事务不进行回滚
在Spring配置文件中进行配置:
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txadvice">
<tx:attributes>
<tx:method name="accountMoney" propagation="REQUIRED"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="pt" expression="execution(* cn.lych4.spring5.service.UserService.*(..))"/>
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
aop:config>
@Configuration //配置类
@ComponentScan(basePackages = "cn.lych4.spring5") //开启组件扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
//创建数据库连接池
//
//
//
//
//
//
//
//
//
//
//
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql// //user_db");
druidDataSource.setUsername("root");
druidDataSource.setPassword("");
return druidDataSource;
}
//创建jdbcTemplate对象
//
//
//
//
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//到IOC容器中根据类型找到 dataSource这个对象
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
//
//
//
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
整个Spring5 框架基于Java8,运行时兼容JDK9,许多不建议使用的类和方法被删除
Spring5.0 框架自带了通用的日志封装。 也可以整合其他日志框架
1. 引入jar包
2. 创建log4j2.xml 必须是这个名字
代码示例
<configuration status="INFO">
<appenders>
<console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
console>
appenders>
<loggers>
<root level="info">
<appender-ref ref="Console"/>
root>
loggers>
configuration>
这样执行代码就会自动输出一些日志,也可以在控制台手动输出日志
public class UserLog {
private static final Logger log = LoggerFactory.getLogger(UserLog.class);
public static void main(String[] args) {
log.info("Hello log4j2");
log.warn("Hello log4j2");
}
}
@Nullable 注解可以使用在方法上面、属性上面、参数上面,表示方法返回可以为空、属性值可以为空、参数值可以为空。
如果发现有错误的地方,欢迎大家提出批评指正
致力于分享记录各种知识干货,关注我,让我们一起进步,互相学习,不断创作更优秀的文章。
不要忘了三连哦 ⭐️