logo头像

分享技术,品味人生

学习Spring5 基础,看这篇就够了

Spring5的内容概述

spring是基于java的又一里程碑产品,几乎是java开发的标配和规范,其中最基础的framework核心是ioc和aop,发展出了web、mvc、security、boot、cloud等全家桶,还有面向主流中间件的data redis、mongodb等产品。

1、Spring概念

(1)、Spring是轻量级的开源的JavaEE框架。

(2)、Spring可以解决企业应用开发的复杂性

(3)、Spring有两个核心部分:

  • IoC:控制反转,把创建对象过程交给Spring管理(包括创建、初始化、使用、销毁全生命过程)

  • Aop:面向切面,不修改源代码进行功能增强,如统一日志处理、事务处理、权限控制等;

(4)、Spring特点

  • 方便解耦,简化开发
  • Aop编程支持
  • 方便程序测试
  • 方便和其他框架进行整合(但目前情况基本上是用spring全家桶居多,其他第三方辅助为主)
  • 方便进行事务操作
  • 降低api开发难度

2、IoC容器

控制反转 invert of control,将对象的创建进行反转,常规情况下,对象都是由开发者手动创建,使用ioc开发者不再需要创建对象,而是由ioc容器根据需要自动创建需要的对象,通过spring潜规则+用户配置,简化过程

(1)、ioc底层原理

ioc 控制反转,把创建对象、对象之间的组装调用过程交给spring进行管理。

使用ioc的目的:为了耦合度降低,提高效率,降低编码和维护的工作量和复杂度。

入门案例:a调用b,升级为a调用工程c,间接执行了b的方法。 简单地说,就是第三代理人解耦

ioc实现的三个条件:xml解析、工厂模式、反射

(2)、ioc接口(beanfactory及子接口applicationContext)

beanfactory是ioc容器的基本实现,常用于spring内部,一般不建议项目开发中直接使用。

applicationContext是beanfactory的子类,常用于项目中,与父类的区别在于加载时就创建对象,虽然启动耗时,但使用时较快;另外功能也比父类丰富得多。

(3)、ioc操作bean管理(基于xml)

bean的管理主要指对象的创建和注入两个操作实现方式有通过xml和注解两种方式。

基于xml的三种注入方式:set、构造函数、p命名空间(注意下方要引入p)

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        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
        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/context/spring-aop-4.3.xsd">

    <!-- 同一个vo做了property的set注入和p命名空间的注入,所以用id进行区分,调用时采用“name”的形式,而非xxx.class -->
    <!-- bean早期有流行用name而非id的属性,这个主要用于struts1命名斜杠时使用 -->
    <bean class="cool.gongju.ioc.Book"  id="book1">        	
        <property name="name" value="张三是怎么练成的" />
        <property name="pages" value="500页" />
    </bean>

    <!-- 构造函数还可以用index,按顺序注入 -->
    <bean class="cool.gongju.ioc.Book2">
        <constructor-arg name="name" value="张三是怎么练成的" />
        <constructor-arg name="pages" value="499页" />
    </bean>

    <bean class="cool.gongju.ioc.Book" id="book2" p:name="张三是怎么练成的" p:pages="488页"></bean>
</beans>

注入空值、特殊符号

<bean class="cool.gongju.ioc.Book"  id="book3">
    <!-- 像大于号、小于号还可以用html转义字符 &lt;&gt; -->
    <property name="name">
        <value><![CDATA[<<张三是怎么练成的>>]]]]></value>
    </property>
    
    <property name="pages">
        <null/>
    </property>
</bean>

注入外部bean、内部bean和级联赋值

<!-- 外部bean -->
<bean class="cool.gongju.dao.UserDao"  id="userDao"/>
<bean class="cool.gongju.service.UserService"  id="userService">
    <property name="userDao" ref="userDao"/>
</bean>

<!-- 内部bean -->
<bean class="cool.gongju.service.UserService"  id="userService">
    <property name="userDao">
        <bean class="cool.gongju.dao.UserDao"  id="userDao">
            <property name="name" value="xxx"/>
        </bean>
    </property>
</bean>

<!-- 级联赋值写法1 -->
<bean class="cool.gongju.dao.UserDao"  id="userDao">    
    <property name="name" value="xxx"/>
</bean>
<bean class="cool.gongju.service.UserService"  id="userService">
    <property name="userDao" ref="userDao"/>
</bean>

<!-- 级联赋值写法2-->
<bean class="cool.gongju.dao.UserDao"  id="userDao"/>
<bean class="cool.gongju.service.UserService"  id="userService">
    <property name="userDao" ref="userDao"/>
    <property name="userDao.name" value="xxx"/>
</bean>

注入集合类属性:包括数组、list、map、set

<!-- 如果需要注入外部对象,就把value换成ref -->
<bean class="cool.gongju.collectionType.Stu" id="stu">
    <property name="courses">
        <!--还可以用list-->
        <array>
            <value>java课程</value>
            <value>spring</value>
            <value>mqsql</value>
        </array>
    </property>
    <property name="list">
        <list>
            <value>xxx</value>
            <value>yyy</value>
        </list>
    </property>
    <property name="maps">
        <map>
            <entry key="aa" value="bb"></entry>
            <entry key="bb" value="cc"></entry>
        </map>
    </property>
    <property name="sets">
        <set>
            <value>xxx</value>
            <value>yyy</value>
            <value>xxx</value>
        </set>
    </property>
</bean>

注入集合类属性:用util标签优化

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:p="http://www.springframework.org/schema/p"
        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/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/context/spring-aop-4.3.xsd">

    <util:list id="courses">
        <value>java课程</value>
        <value>spring</value>
        <value>mqsql</value>
    </util:list>
    <util:list id="list">
        <value>xxx</value>
        <value>yyy</value>
    </util:list>
    <util:map id="maps">
        <entry key="aa" value="bb"></entry>
        <entry key="bb" value="cc"></entry>
    </util:map>
    <util:set id="sets">
        <value>xxx</value>
        <value>yyy</value>
        <value>xxx</value>
    </util:set>

    <bean class="cool.gongju.collectionType.Stu" id="stu">
        <property name="courses" ref="courses"></property>
        <property name="list" ref="list"></property>
        <property name="maps" ref="maps"></property>
        <property name="sets" ref="sets"></property>
    </bean>
</beans>

返回工厂bean:通常情况下,xml中配置的class就是返回的类型,如果要修改,那么实现FactoryBean的getObject方法

package cool.gongju.factoryBean;

import cool.gongju.dao.UserDao;
import org.springframework.beans.factory.FactoryBean;

/**
 * @className: Mybean
 * @description: 返回工厂bean
 * @author: eric 4575252@gmail.com
 * @date: 2022/7/21/0021 17:35:31
 **/
public class Mybean implements FactoryBean {
    public UserDao getObject() throws Exception {
        UserDao dao = new UserDao();
        return dao;
    }

    public Class<?> getObjectType() {
        return null;
    }

}

bean的作用域:设置单实例(默认)或多实例

如果设置scope为prototype,则bean是多实例,如果被反复调用hash地址会变动,而且是每次调用时才新建

<!-- scope有prototype和singleton -->
<bean id="mybean" class="cool.gongju.factoryBean.Mybean" scope="prototype"></bean>

bean的生命周期:常规五步,构造、填充属性、初始化、正常用、销毁,还有隐藏两步初始化前后,共七步

bean的创建到销毁就是bean的完整生命周期,具体如下:

  • 通过构造器创建bean实例(无参数构造)
  • 为bean的属性设置值,其他bean引用(调用set)
  • 调用bean的初始化方法(需要单独设置)
  • bean可以使用了(对象获取到了)
  • 当容器关闭时,调用bean的销毁方法(需要单独配置)
<bean id="order" class="cool.gongju.ioc.Order" init-method="init" destroy-method="destory">
    <property name="oname" value="老张下单了"/>
</bean>
package cool.gongju.ioc;

public class Order {
    private String oname;

    public Order() {
        System.out.println("第一步,调用了构造函数");
    }

    public void setOname(String oname) {
        this.oname = oname;
        System.out.println("第二步,对属性值进行填充");
    }

    public void hello(){
        System.out.println("第4步,调用了方法");
    }

    public void init(){
        System.out.println("第3步,调用了初始化");
    }

    public void destory(){
        System.out.println("第5步,调用了销毁");
    }
    
    @Test
    public void testBeanScope(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
        context.getBean("order",Order.class).hello();
        context.close();
    }
}

bean初始化的后置处理器:不要忘了到xml中进行像普通bean声明!

package cool.gongju.ioc;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;


/**
 * @className: MyPost
 * @description: TODO 类描述
 * @author: eric 4575252@gmail.com
 * @date: 2022/7/21/0021 18:05:51
 **/
public class MyPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化前被调用了");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化后被调用了");
        return bean;
    }
}

bean的自动装配

<!-- 通过autowire进行自动装配,有byName和byType两种,如果属性接口有多个实现类,并都声明为bean则只能用byName -->
<bean id="dept" class="cool.gongju.autowire.Dept"/>
<bean id="dept2" class="cool.gongju.autowire.Dept2"/>
<bean id="emp" class="cool.gongju.autowire.Emp" autowire="byName"/>

bean引入外部配置:很常用,如引入德鲁伊的连接池配置

<!-- 注意!要引入context的xsi,然后声明配置文件路径,再用美元符号+花括号进行引用 -->
<bean id="dept" class="cool.gongju.autowire.Dept"/>
<bean id="dept2" class="cool.gongju.autowire.Dept2"/>
<bean id="emp" class="cool.gongju.autowire.Emp" autowire="byName">
    <property name="name" value="${prop.test}"/>
</bean>

<context:property-placeholder location="classpath:test.properties"/>

(4)、ioc操作bean管理(基于注解)

注解是特殊代码标记,格式@xxx[(A=B,C=D)]

注解可以作用于类、方法、属性上。

注解的使用目的是简化xml的配置。

基于注解实现对象创建:在xml中配置扫描包配置,类加上专用注解,四选一、任选,建议见名知意 component、controller、service、repository

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        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
        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/context/spring-aop-4.3.xsd">

    <context:component-scan base-package="cool.gongju" />
</beans>
package cool.gongju.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    //注解上定义id用value做key,不写默认采用小字母开头的驼峰命名法
}

扫描包细化设置:包含、不包含

<context:component-scan base-package="cool.gongju" >
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

属性注入

  • autowired,根据类型注入
  • qualifier,根据名称注入,作用于autowired之上,组合使用!
  • resource,同时兼具上面两个,替代上面的组合,这个注解是javax中的,spring不建议使用!
  • value,注入普通类型属性
// 默认按class类型搜索匹配
@Autowired
private UserDao userDao;

// 如果有多个实现类,则按名字区分,dao那一头也要提前指定好相应的名称
@Autowired
@Qualifer(name="userdao")
private UserDao userDao;


// 这里不写name按Autowired使用,用了就等同组合效果
@Resource(name="userdao")
private UserDao userDao;


// 对name进行普通属性的注入
@Value(Value="abc")
private String name;

完全注解开发:创建配置类,测试或启动的时候用AnnatationContext来加载!

package cool.gongju;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "cool.gongju")
public class SpringConfig {
}


// Test code

    @Test
    public void test2(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        System.out.println(context.getBean("userService", UserService.class));
    }

3、Aop

面向切面编程,降低耦合度、提高重用性、提高开发效率。不通过修改代码的形式,增加新功能,如添加统一权限控制、日志输出、异常处理等。

底层原理

有两种实现方式,有接口和实现类采用jdk的proxy动态代理,只有父子类的用cglib动态代理

案例:jdk动态代理演示

public interface UserDao {
    public int add(int a, int b);
    public String update(String id);
}

public class UserDaoImpl implements UserDao {
    @Override
    public int add(int a, int b) {
        System.out.println("add方法被调用了");
        return a+b;
    }

    @Override
    public String update(String id) {
        return null;
    }
}

public class TestJdk {

    @Test
    public void testJdkAspect(){
        UserDao userDao = new UserDaoImpl();

        Class[] interfaces={UserDao.class};
        UserDao dao = (UserDao)Proxy.newProxyInstance(TestJdk.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
        System.out.println(dao.add(1, 2));
    }

    class UserDaoProxy implements InvocationHandler{

        private Object obj;

        public UserDaoProxy(Object obj) {
            this.obj = obj;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("前置调用");
            Object res = method.invoke(obj, args);
            System.out.println("后置调用");
            return res;
        }
    }
}

Aop术语:

  • 连接点,类中可以被切入的点都是连接。
  • 切入点,实际被增强的方法,通过表达式进行过滤
  • 通知(增强),实际增强的部分,有5种形式,前置、后置、环绕、异常、最终通知。
  • 切面,把通知应用到切入点的过程

Aop操作准备:

  • Spring框架基于AspectJ进行切面操作,这个属于独立框架,不是Spring内部
  • 基于AspectJ实现AOP操作有两种方式,XML和注解(主流)
  • 切入点表达式 execution(权限修饰符 [返回类型] 类全路径.方法(参数列表)),可用通配符*和.
    • 其中权限修饰符、类部分路径、方法可用*号做通配符
    • 参数列表一般用两个.做模糊匹配
    • 返回类型可不写

AspectJ基于XML示例

aspectj的使用需要引入spring context和 aspectj weaver,编写子类和代理类,然后配置xml,xml主要有bean的声明、切入点表达式和增强方法

其中:1、配置部分

<!-- pom依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.20</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>


<!-- 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"
        xmlns:aop="http://www.springframework.org/schema/aop"
        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
        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-4.3.xsd">

    <bean id="bean" class="cool.gongju.xml.Bean"/>
    <bean id="beanProxy" class="cool.gongju.xml.BeanProxy" />

    <aop:config>
        <aop:pointcut id="p" expression="execution(* cool.gongju.xml.Bean.add(..))"/>
        <aop:aspect ref="beanProxy">
            <aop:before method="before" pointcut-ref="p"/>
        </aop:aspect>
    </aop:config>

</beans>

2、java部分

public class Bean {
    public int add(int a, int b){
        System.out.println("add 方法被调用");
        return a+b;
    }
}

public class BeanProxy {
    public void before(){
        System.out.println("BeanProxy before。。。");
    }
}

AspectJ基于注解示例

引入pom依赖,再做包扫描和切面自匹配,然后bean都加上componet,切面类加aspect注解就ok啦

其中:1、配置部分

<!-- pom依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.20</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>


<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns: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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

    <context:component-scan base-package="cool.gongju.anno"/>
    <aop:aspectj-autoproxy/>
</beans>

2、java 部分

@Component
public class User {
    public int add(int a, int b){
        System.out.println("add 方法被调用");
        return a+b; //换成10/0触发异常
    }
}

@Component
@Aspect
public class UserProxy {

    @Before(value = "execution(* cool.gongju.anno.User.add(..))")
    public void before(){
        System.out.println("UserProxy before 前置通知。。。");
    }


    @AfterReturning(value = "execution(* cool.gongju.anno.User.add(..))")
    public void afterReturning(){
        System.out.println("UserProxy afterReturning 后置通知。。。");
    }

    @Around(value = "execution(* cool.gongju.anno.User.add(..))")
    public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        System.out.println("UserProxy around before 环绕通知。。。");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("UserProxy around after 环绕通知。。。");
        return proceed;
    }

    @AfterThrowing(value = "execution(* cool.gongju.anno.User.add(..))")
    public void afterThrowing(){
        System.out.println("UserProxy afterThrowing 异常通知。。。");
    }

    @After(value = "execution(* cool.gongju.anno.User.add(..))")
    public void after(){
        System.out.println("UserProxy after 最终通知。。。");
    }
}

@Test
public void testAspectJAnno(){
    ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
    User b = context.getBean(User.class);
    System.out.println(b.add(1, 2));
}

Aspect 相同切入点抽取去除冗余

用于冗余代码清理,一次编写,方便统一维护

    @Pointcut(value = "execution(* cool.gongju.anno.User.add(..))")
    public void pointCut(){}

    @Before(value = "pointCut()")
    public void before(){
        System.out.println("UserProxy before 前置通知。。。");
    }


    @AfterReturning(value = "pointCut()")
    public void afterReturning(){
        System.out.println("UserProxy afterReturning 后置通知。。。");
    }

Aspect 多增强类,对相同方法设置优先级

在多个增强类上加Order注解,范围在0-正无穷,越小优先级越高

@Component
@Aspect
@Order(2)
public class UserProxy2 {

    @Pointcut(value = "execution(* cool.gongju.anno.User.add(..))")
    public void pointCut(){}

    @Before(value = "pointCut()")
    public void before(){
        System.out.println("UserProxy2 before 前置通知。。。");
    }

}

Aspect 全注解

只要增加一个配置类即可

@Configuration
@ComponentScan(basePackages = "cool.gongju.anno")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig {
}

@Test
public void testAspectJAnno2(){
    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    User b = context.getBean(User.class);
    System.out.println(b.add(1, 2));
}

4、JdbcTemplate

准备工作

  • 引入druid、mysql、jdbc的依赖,数据库表准备

测试案例:

  • 对单条行记录进行增加、修改、删除
  • 查询单个实体(行)、单个值(如总记录数)、列表(全表)
  • 批量操作,这里演示了批量添加,修改或删除也是类似,这里略过
<!-- pom -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.20</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.20</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.11</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>

<?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.xsd">

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://192.168.20.164:3306/userdb" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
    </bean>

    <context:component-scan base-package="cool.gongju.spring5"/>
</beans>

sql

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

java

@ToString
@Data
public class User {
    private String userId;
    private String userName;
    private String status;
}

public interface UserDao {

    void addUser(User user);

    void updateUser(User user);

    void delUser(String id);

    void findUserByID(String id);

    void findUserCount();

    void findAllUser();

    void batchAdd();
}

@Repository
public class UserDaoImpl implements UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void addUser(User user) {
        String sql =  "insert into t_user values(?,?,?)";
        int update = jdbcTemplate.update(sql, user.getUserId(), user.getUserName(), user.getStatus());
        System.out.println(update);
    }

    @Override
    public void updateUser(User user) {
        String sql =  "update t_user set user_name=?,status =? where user_id =?";
        int update = jdbcTemplate.update(sql, user.getUserName(), user.getStatus(), user.getUserId());
        System.out.println(update);

    }

    @Override
    public void delUser(String id) {
        String sql =  "delete from t_user where user_id = ?";
        int update = jdbcTemplate.update(sql, id);
        System.out.println(update);
    }

    @Override
    public void findUserByID(String id) {
        String sql =  "select * from t_user where user_id = ?";
        User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), id);
        System.out.println(user);
    }

    @Override
    public void findUserCount() {
        String sql =  "select count(*) from t_user";
        Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println(integer);
    }

    @Override
    public void findAllUser() {
        String sql =  "select * from t_user";
        List<User> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
        System.out.println(list);
    }

    @Override
    public void batchAdd() {
        String sql =  "insert into t_user values(?,?,?)";
        List<Object[]> list = new ArrayList<Object[]>();
        list.add(new Object[]{"3", "java", "heihei"});
        list.add(new Object[]{"4", "c++", "hoho"});
        list.add(new Object[]{"5", "php", "hehe"});
        int[] ints = jdbcTemplate.batchUpdate(sql, list);
        System.out.println(Arrays.toString(ints));
    }
}


@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    public void addUser(){
        User user = new User();
        user.setUserId("1");
        user.setUserName("张三");
        user.setStatus("ok");
        userDao.addUser(user);
    }

    public void delUser(){
        userDao.delUser("1");
    }

    public void updateUser(){
        User user = new User();
        user.setUserId("1");
        user.setUserName("李四");
        user.setStatus("ok");
        userDao.updateUser(user);
    }

    public void findUserByID(){
        userDao.findUserByID("1");
    }

    public void findUserCount(){
        userDao.findUserCount();
    }

    public void findAllUser(){
        userDao.findAllUser();
    }

    public void batchAdd() {
        userDao.batchAdd();
    }
}

5、事务管理

事务是数据库操作的一个基本单元,一般包含一个或多个任务,执行时要么一起成功,要么一起失败,例如在转账时两人的账号余额加减变化!

事务的四个特性(ACID):

  • 原子性,(Atomicity)事务中的多个任务要么成功一起提交,要么失败回滚到事务执行初期的状态。
  • 一致性,(Consistency)事务进行改变的前后,总体不变,如两人余额的总数在转账前后都是一致的,不会凭空多一些或少一些。
  • 隔离性,(Isolation)多个事务之间是不会互相影响,主要指某个事务会不会读到另一个事务未提交的数据。
  • 持久性,(Durability)事务执行后数据会被持久化,如写入磁盘文件或数据库

事务的操作:建议控制在service层,有传统编程式控制、spring xml或注解的声明式控制,依赖的是aop的技术

并发事务的隔离控制,事务并发操作时,隔离性可能存在三种问题:

  • 脏堵,事务B读取到事务A修改后但未提交的数据,事务A发生回滚,事务B之前拿到的数据是脏数据。
  • 不可重复读,并发修改时,事务AB初期拿到一样的数据,如果两者前后对数据进行修改,导致数据错乱,应串行读取、修改。
  • 幻读,两个事务并行处理时,事务A提交新增数据的前后,事务B两次查询的记录总数不一致。

spring事务的默认隔离参数为Repeatable Read,限制了脏读和不可重复读,幻读需要设置为串行才可彻底解决,不过牺牲了性能,可参考mysql mvcc版本控制技术!

事务中可设置参数:声明式事务管理参数配置

  • 传播属性,propagation,参考下图,默认Required

image-20220722163104950

  • 事务隔离级别,ioslation,默认Repeatable Read

image-20220722163039997

  • 只读,readOnly 默认非只读,可查询、可修改;可设置为只读true,只能查询
  • 超时,timeOut 默认-1,不超时,可设置数字1-正无穷,单位秒
  • 回滚,rollbackFor,设置回滚触发的异常类class
  • 不会滚,noRollbackFor,设置不回滚触发的异常类class

传统java中jdbc的事务控制: 通过try、catch、finally和jdbc的事务手工控制进行操作。

@Test
public void demo2(){
    //模拟转账操作,使用事务管理
    Connection conn = null;
    PreparedStatement stmt = null;
    try {
        conn = JDBCUtils.getConnection();
        //在连接获得后,开启事务
        conn.setAutoCommit(false);    //关闭自动提交
        String sql1 = "update account set money = money - 100 where name = 'aaa'";
        String sql2 = "update account set money = money + 100 where name = 'bbb'";
        
        stmt = conn.prepareStatement(sql1);
        stmt.executeUpdate();
        
        int d = 1 / 0;
        
        stmt = conn.prepareStatement(sql2);
        stmt.executeUpdate();
        
        //如果程序能走到这一步  说明两句sql都执行成功    提交事务
        conn.commit();
    } catch (Exception e) {
        //表示在执行转账的过程产生了异常    需要将两句sql语句进行回滚
        try {
            conn.rollback();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
        e.printStackTrace();
    }
}

基于xml+注解的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"
        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/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">

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://192.168.20.164:3306/userdb" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 组合式主要用到这个注解驱动+类上面的注解标准-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <context:component-scan base-package="cool.gongju.spring5"/>
</beans>

java部分

package cool.gongju.spring5.service;

import cool.gongju.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

//主要用到了 Transactional标记
@Service
@Transactional
public class UserService {
    @Autowired
    private UserDao userDao;

    public void moneyChange(){
        userDao.addMoney();
        // 手工设置异常的生成
        int i = 10/0;
        userDao.reduceMoney();
    }

}

基于xml的spring事务控制

service上的事务注解可以拿掉了

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://192.168.20.164:3306/userdb" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!--  这里不需要注解驱动了   <tx:annotation-driven transaction-manager="transactionManager"/>-->
    
    <!-- 设置在那个方法上设置何种事务-->
    <tx:advice id="txadvice">
        <tx:attributes>
            <tx:method name="moneyChange" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!-- 设置切入点、切面操作方式 -->
    <aop:config>
        <aop:pointcut id="pt" expression="execution(* cool.gongju.spring5.service.UserService.*(..))"/>
        <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
    </aop:config>


    <context:component-scan base-package="cool.gongju.spring5"/>
</beans>

基于完全注解的spring事务控制

增加配置类,这里对数据库配置还有硬编码,在springboot中会从配置文件中加载!

package cool.gongju.spring5;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

/**
 * @className: TxConfig
 * @description: TODO 类描述
 * @author: eric 4575252@gmail.com
 * @date: 2022/7/22/0022 17:01:02
 **/
@Configuration
@ComponentScan(basePackages = "cool.gongju.spring5")
@EnableTransactionManagement
public class TxConfig {

    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://192.168.20.164:3306/userdb");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }

    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}


@Test
public void testChangeMoney2(){
    ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
    UserService userService = context.getBean(UserService.class);
    userService.moneyChange();
}

6、Spring5新特性

log4j2

<!-- POM -->
<!-- log4j-core -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.15.0</version>
</dependency>
<!-- log4j-api -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.15.0</version>
</dependency>
<!-- log4j-slf4j-impl -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.15.0</version>
    <scope>test</scope>
</dependency>
<!-- slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.32</version>
</dependency>


<!-- log4j2.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration 后面的 status 用于设置 log4j2 自身内部的信息输出,可以不设置,当设置成 trace 时,可以看到 log4j2 内部各种详细输出-->
<configuration status="INFO">
    <!--先定义所有的 appender-->
    <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>
    <!--然后定义 logger,只有定义 logger 并引入的 appender,appender 才会生效-->
    <!--root:用于指定项目的根日志,如果没有单独指定 Logger,则会使用 root 作为默认的日志输出-->
    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

java 中使用

package cool.gongju.spring5;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4JTest {

    private static final Logger log = LoggerFactory.getLogger(Log4JTest.class);

    public static void main(String[] args) {
        log.info("hello,dengzi");
        log.warn("hello,dengzi");
    }
}

Junit4

<!-- pom -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.20</version>
</dependency>

java, 可以选择用xml或全注解的方式

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TxConfig.class)


//@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(locations = {"classpath:bean.xml"})
//@ContextConfiguration("classpath:bean.xml")
public class TestJdbc {
    @Autowired
    private UserService userService;

    @Test
    public void testChangeMoney2(){
        userService.moneyChange();
    }

Junit5

以下配置用于spring framework, 如果是springboot那是另一种写法, 参考 SpringBoot与单元测试JUnit的结合 - 小鱼吃猫 - 博客园 (cnblogs.com)

<!-- pom -->

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.8.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.20</version>
</dependency>

java, 可以选择用xml或全注解的方式, 注意,这里的Test标签是不一样的!

import cool.gongju.spring5.service.UserService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = TxConfig.class)

//@ExtendWith(SpringExtension.class)
//@ContextConfiguration(locations = {"classpath:bean.xml"})
public class TestJdbc {

    @Autowired
    private UserService userService;

    @Test
    public void testChangeMoney2(){
        userService.moneyChange();
    }
}

评论系统未开启,无法评论!