Spring
1.1 简介
full-stack轻量级开源J2EE框架,以IOC和AOP为内核
1.2 Spring开发步骤(quick start)
-
在maven中导入spring坐标。核心依赖有core、beans、context、expression。而引入mvc不仅包含这四个还附赠aop和mvc即web相关依赖,何乐不为?且要引入junit,四版本,为了测试(@Test用于方法上);日志也顺便引入了
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.12</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> </dependencies>
-
在bean.xml中创建bean(dao、service,接口和实现等)
<bean id="user" class="ind.deng.spring5.User"/>
-
创建applicationContext.xml
-
在配置文件中配置
-
创建ApplicationContext对象getBean
因为初始只做个简单测试即可,345步直接跳过,用Test方法测试即可:
public class TestUser {
@Test
public void testAdd(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");//加载配置文件
User user = context.getBean("user", User.class);//获取容器中的对象
System.out.println(user);
user.add();
}
}
2.1 IOC(控制反转)
是为了降低耦合度的
对象由spring创建,管理,装配;或者说对象之间的创建和调用由spring管理
2.2 注解
注:先学xml配置文件方式再学注解是比较合理的方式
要是用注解要引入spring-aop依赖,而我们之前已经一劳永逸了
2.2.1 bean的创建
@Component:说明此类已被注册到Spring容器中
dao层等价的是@Repository
service层等价的是@Service
控制器层为@Controller
上面四个组件功能一致,用来将bean注入到容器中,用的地方不一样,尽量别乱用
使用注解要在xml配置文件中引入context命名空间并加上扫包的步骤:
<context:component-scan base-package="包路径"></context:component-scan>
要扫描多个包,路径可以用“,”隔开,或者扫描上层包
比如,Controller层的注解:
当只有value属性时,可以直接写@Controller(“test01”)
也可以直接写@Controller,此时bean的id为类的名称首字母小写:myController
@Controller(value="test01")
public class MyController {
public void test01(){
System.out.println("controller...");
}
}
包扫描可以更加细致更加定制化一些
<context:component-scan base-package="ind.deng.spring5" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
use-default-filters默认为true,使用spring默认的filter。设置为false后可以自定义filter(只扫描给定的组件)。上例是过滤ind.deng.spring5包中所有@Controller注解。能看懂即可
<context:component-scan base-package="ind.deng.spring5.controller">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
和上面的例子相反,没有use-default-filters=“false”,扫描所有组件,除了<context:exclude-filter type="" expression=""/>指定的内容
2.2.2 bean的属性注入
@Autowired自动装配(byType)
@Qualifier (byName)
@Resource 先byName,找不到再byType。
上面三个是对象属性注入。小细节:前两个是spring自带的注解,第三个是javax中的扩展注解,所以spring更建议使用前两个
例子:
@Service
public class UserService {
//不需要set方法了
//没有写value值则按类型注入;
//写value值,如果UserDaoImpl实现类@Repository注解没写value,那这里的value值就是userDaoImpl
//否则两者必须匹配才能按名称注入
@Resource
private UserDao userDao;
public void update(){
System.out.println("update...");
userDao.add();
}
}
@Value(“”)注入属性值 常结合SpEL(spring表示式语言,el的升级版)注入配置文件中的值
2.2.3 完全注解开发
以上注解不能全部替代xml,比如非自定义的bean(第三方的类)、加载配置文件、组件扫描、import等。有一些新的注解
@Configuration:说明此类是配置类(也已经被注册到Spring容器中),就像xml配置文件。类中有方法。方法前@Bean对应bean标签,方法名相当于id属性,返回值相当于class属性
@ComponentScan用于包扫描
Test中用AnnotationConfigApplicationContext获取Spring容器
@PostConstruct与@PreDestroy等同于init和destroy
还有其他杂七杂八的,有的配起来反而比xml麻烦。这种纯注解其实就是springboot采用的方式。在这里还是xml和注解结合最方便
注解能解析el表达式,但普通的API不行。常用的注解也就十来个。springboot中比较多
2.4 底层原理
xml解析、工厂模式、反射(得到类的字节码文件以操作类的所有内容,Class.forName())
工厂模式只能实现部分解耦,如工厂生产dao实例(return new),service用工厂类的静态方法获取dao对象,降低了dao和service的耦合度,但工厂和dao又耦合在一起了.
而用了以上三种方法后,耦合度可以进一步降低
public class UserFactory {
public static User getUser() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
String classValue= "通过xml解析获取bean中class的值,即那个全类名";
Class clazz = Class.forName(classValue);
return (User) clazz.newInstance();
}
}
因为即使bean改变了,工厂仍能正确获取该对象。注意上面没有写出xml解析的细节,且newInstance()方法已经过时。
知道这是spring底层原理就足够了。底层是什么?是对象工厂
2.5 两种实现方式(两个接口)
BeanFactory:IOC容器的基本实现,设计spring时使用的,不是给开发人员用的(也能用);加载配置文件时不创建对象,而是使用时才创建。
ApplicationContext:是前者的子接口,功能更强大,开发使用这个;加载配置文件时就会创建。因为一般都是web项目,在服务器启动时就加载好更省事,访问时就快了
2.6 ApplicationContext的两个常用实现类
ClassPathXmlApplicationContext参数是类路径src下(或者resources下)擦皮鞋方便记忆
FileSystemXmlApplicationContext参数是绝对路径(盘符之类打的)
2.7 Bean管理
指spring创建对象和注入属性。创建对象时默认执行无参构造方法,没有则报错
2.7.1 Bean标签中的属性
id
找到注入的对象的唯一标识符
name
早期属性,和id功能差不多,但name中可以有些特殊符号,较少使用
class
值为全类名,将指定类的对象注入到容器中
scope(作用域)
常用singleton(单例,只创建一个对象,多次使用;xml被加载时,实例化bean)和prototype(多例,每次新建一个对象;getBean时,实例化bean)。默认单例。还有不太常用的request和session,就是把创建的对象放在这两个域中。
init-method和destroy-method
init-method=“方法名”;
destroy-method=“方法名”;
2.7.2 Bean实例化三种方式
- 无参构造方法:bean中指定id和class
- 工厂静态方法:创建工厂类及其中的静态方法返回需要的对象。xml中class为工厂类,后面跟着factory-method属性,值为静态方法
- 工厂实例方法:方法不是static,所以先实例化工厂对象再使用方法,用两个bean(麻烦)。factory-bean、factory-method
2.8 依赖注入(DI)
是IOC的一种具体实现。就是注入属性。IOC实现解耦,但代码间仍有依赖关系,如service需要用到dao。而这种依赖交给spring管理,把dao注给service,我们直接调service就行了(外部bean注入)
依赖注入三种方式:
-
setXxx注入:
<property name="" value=""></property>
-
有参构造函数注入:
<constructor-arg name="" value=""></constructor-arg>
也可以把name换成index以按照类中属性的顺序注入,但很少用
-
p命名空间注入。与法一大同小异,更简便些
<bean id="user" class="ind.deng.spring5.User" p:name="邓玉琪"></bean>
类中必须有无参构造,才能在spring中定义bean
将某个属性设成空值:
<bean id="user" class="ind.deng.spring5.User">
<property name="name">
<null/>
</property>
</bean>
如果属性值有特殊字符,如<<>>和标签冲突,可以使用转义 > <等
也可以使用CDATA
<bean id="user" class="ind.deng.spring5.User">
<property name="name">
<value>
<!--CDATA[]内是要输出的值-->
<![CDATA[<<邓玉琪>>]]>
</value>
</property>
</bean>
引入其他配置文件
可以将配置文件按模块拆分为多个,统一引入主配置文件使用
<import resource="xxx.xml"/>
自动装配:约定优于配置。只适用于rel类型
autowire=“byName|byType|constructor”
IOC容器中有一个bean的id值恰好为该bean中的ref属性值,则自动装配.前提:只有一个bean满足此要求,不然无法辨别
constructor类似byType,但支持多个满足要求(和constructor参数一致的)的bean
自动装配减少代码量但降低可读性
2.9 外部bean
用ref属性,在userService中注入userDao属性,这样前者就可以使用后者的方法,再做些其他操作。
<bean id="userDaoImpl" class="ind.deng.spring5.dao.UserDaoImpl"></bean>
<bean id="userService" class="ind.deng.spring5.service.UserService">
<property name="userDao" ref="userDaoImpl"></property>
</bean>
dao层分为接口和实现类,里面有个add方法,比较简单就不贴出来了。service层类:
public class UserService {
//将dao对象当做属性注入。既然是属性,要么有set方法要么有无参构造才能注入并给service使用
//如果只是默认的无参构造的话,就没dao什么事了,那我还要它作为属性干啥?
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void update(){
System.out.println("update...");
//把dao当做属性就是拿来用的
userDao.add();
}
}
2.10内部bean
用实体类表示关系表之间的关系
比如部门和员工是一对多,可以在员工类里声明部门属性,然后采取以下内部bean方式注入
<bean id="emp" class="ind.deng.spring5.bean.Emp">
<property name="ename" value="邓玉琪"/>
<property name="gender" value="男"/>
<property name="dept">
<!--以bean代替value-->
<bean id="dept" class="ind.deng.spring5.bean.Dept">
<property name="dname" value="科研部"/>
</bean>
</property>
</bean>
2.11 级联赋值(在bean注入另一个bean)
法一:外部bean。前面用过,只不过这次给属性赋值了
<bean id="emp" class="ind.deng.spring5.bean.Emp">
<property name="ename" value="邓玉琪"/>
<property name="gender" value="男"/>
<property name="dept" ref="dept">
</property>
</bean>
<bean id="dept" class="ind.deng.spring5.bean.Dept">
<property name="dname" value="科研部"/>
</bean>
法二:
<bean id="emp" class="ind.deng.spring5.bean.Emp">
<property name="ename" value="邓玉琪"/>
<property name="gender" value="男"/>
<property name="dept" ref="dept"/>
<!--emp中的dept必须要有get方法。此处会把dept中原本的dname覆盖掉-->
<property name="dept.dname" value="炊事班">
</property>
</bean>
<bean id="dept" class="ind.deng.spring5.bean.Dept">
<property name="dname" value="科研部"/>
</bean>
2.12 将值注入集合
大同小异。都是property开头,再是集合名称,再是value。map是entry
<bean id="collection" class="ind.deng.spring5.bean.Collections">
<property name="str">
<array>
<value>高数</value>
<value>英语</value>
</array>
</property>
<property name="list">
<list>
<value>c++</value>
<value>java</value>
</list>
</property>
<property name="maps">
<map>
<entry key="数三" value="狗都不考"></entry>
<entry key="外语" value="英语"></entry>
</map>
</property>
<property name="sets">
<set>
<value>不允许</value>
<value>重复</value>
</set>
</property>
</bean>
2.13 将对象注入集合
也就是把上面的value改成现在的ref,然后将已经注册的bean注入到集合中
<bean id="collection" class="ind.deng.spring5.bean.Collections">
<property name="courses">
<list>
<ref bean="course1"/>
<ref bean="course2"/>
</list>
</property>
</bean>
<bean id="course1" class="ind.deng.spring5.bean.Course">
<property name="cname" value="java"/>
</bean>
<bean id="course2" class="ind.deng.spring5.bean.Course">
<property name="cname" value="c++"/>
</bean>
2.14 将集合注入部分提取出来
在配置文件中引入util命名空间
<!--公共部分-->
<util:list id="bookList" >
<value>高数</value>
<value>英语</value>
</util:list>
<bean id="book" class="ind.deng.spring5.bean.Book">
<property name="bookList" ref="bookList"/>
</bean>
2.15 FactoryBean
spring中有两种bean。一种是我们自己定义的普通bean,bean类型就是返回类型;一种是FactoryBean,bean类型可以和返回类型不一样。
使用工厂bean:用一个类实现FactoryBean接口(需要泛型,表示返回对象类型)
public class MyBean implements FactoryBean<Course> {
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("高数");
return course;
}
//上面这个方法比较重要,用来设置返回的对象类型;下面俩方法暂时用不到
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
2.16 Bean生命周期
- 通过构造器创建bean实例(无参构造)
- 为bean属性设置值或引用其他bean(调用set方法)
- 初始化bean(自己配置)
- 使用bean
- 容器关闭时,调用bean的销毁方法(自己配置)
配置文件:
<bean id="orders" class="ind.deng.spring5.bean.Orders" init-method="init" destroy-method="destroy">
<property name="oname" value="奶茶"/>
</bean>
Orders类
public class Orders {
private String oname;
public Orders() {
System.out.println("1.通过构造器创建bean实例");
}
public void setOname(String oname) {
System.out.println("2.为bean属性设置值或引用其他bean(onama='奶茶')");
this.oname = oname;
}
public void init(){
System.out.println("3.初始化bean");
}
public void destroy(){
System.out.println("5.容器关闭时,调用bean的销毁方法");
}
@Override
public String toString() {
return "Orders{" +
"oname='" + oname + '\'' +
'}';
}
}
Test方法(要想销毁bean要调用context的close方法,ClassPathXmlApplicationContext(ApplicationContext的实现类)才有close方法)
@Test
public void testLifeCircle(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("4.使用bean");
System.out.println(orders);
context.close();
}
控制台输出:
后置处理器
加上后置处理器后,第三步即调用init方法前后多了两个处理步骤,bean生命周期变为7步
配置完后置处理器后,配置文件中所有bean初始化时都会执行后置处理器的两个方法
<bean id="orders" class="ind.deng.spring5.bean.Orders" init-method="init" destroy-method="destroy">
<property name="oname" value="奶茶"/>
</bean>
<bean id="processor" class="ind.deng.spring5.bean.Processor"/>
选择一个类实现BeanPostProcessor接口
public class Processor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("2.5.前置处理器");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("3.5.后置处理器");
return bean;
}
}
测试方法无变化。控制台输出:
2.17 自动装配
根据不同方式,根据属性名称(byName)或属性类型(byType)自动注入
byName:Dept的bean的id值必须与Emp类中的属性值(dept)同名。
<bean id="emp" class="ind.deng.spring5.bean.Emp" autowire="byName"/>
<bean id="dept" class="ind.deng.spring5.bean.Dept"/>
byType代码就不贴了。如果有多个class一样的bean则autowire="byType"则会报错,而用byName就不会有这种顾虑,因此常用byName,匹配也更精准。实际使用时常用注解,省事方便
3.1 AOP(面向切面编程)
不改变源代码而在主干功能中添加新功能添加新功能。可以将业务逻辑的各个部分隔离,使它们间的耦合度降低,提高代码重用
底层基于动态代理
有接口要实现增强,用JDK动态代理,创建接口实现类的代理对象增强方法;
无接口要实现增强,用CGLIB动态代理,创建子类的代理对象增强方法。
3.2 JDK实现动态代理
使用Proxy类里的静态方法newProxyInstance()创建代理对象
方法中有三个参数:
- 类加载器ClassLoader
- 增强方法所在类实现的接口,支持多个接口。类<?>[] interfaces
- InvocationHandler。实现这个接口,创建代理对象,写增强的部分
JDK实现动态代理步骤:
- 创建接口,定义方法
- 创建接口实现类,实现方法
- 使用Proxy类创建接口代理对象
public class JDKProxy {
public static void main(String[] args) {
Class[] interfaces = {UserDao.class};
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
dao.add(1,2);
}
}
//这是个代理类,通过有参构造获取要代理的对象
public class UserDaoProxy implements InvocationHandler {
//代理谁的对象,把谁传过来
private Object obj;
public UserDaoProxy(Object obj){
this.obj=obj;
}
//增强的逻辑写在invoke中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法之前执行"+method.getName()+",传递的参数"+ Arrays.toString(args));
Object res = method.invoke(obj,args);
System.out.println("方法之后执行"+obj);
return res;
}
}
3.3 AspectJ
一些术语:
连接点:类中可以被增强的方法(备胎)。
切入点:真正被增强的方法(正宫)。
通知(增强):实际增强的逻辑部分
通知的类型:前置通知、后置通知、环绕通知、异常通知、最终通知
切面:是一个动作,指把通知应用到切入点的过程
切入点表示式
execution([权限修饰符] 返回值类型.全类名.方法名.(参数列表))
权限修饰符可省略,返回值类型、全类名、方法名都可以用*代替代表任意,参数列表用两个点…表示任意参数
在spring中一般使用AspectJ实现AOP,但AspectJ是独立于AOP的
3.3.1 注解方式实现AOP
- 在配置文件中开启组件扫描(配置文件中要引入context和aop命名空间)
<context:component-scan base-package="ind.deng.spring5.aopanno"/>
- 用注解创建待增强和增强类的对象
@Component
//待增强的类
public class User {
public void add(){
System.out.println("add...");
}
}
- 增强类上面加@Aspect注解
@Component
@Aspect
//增强的类
public class UserProxy {
public void before(){
System.out.println("before...");
}
}
- 配置文件中开启生成代理对象
<aop:aspectj-autoproxy/>
- 在通知上加上对应注解和切入点表达式
@Component
@Aspect
//增强的类
public class UserProxy {
//前置通知
@Before("execution(* ind.deng.spring5.aopanno.User.*(..))")
public void before(){
System.out.println("before...");
}
//后置通知
@AfterReturning("execution(* ind.deng.spring5.aopanno.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning...");
}
//环绕通知
@Around("execution(* ind.deng.spring5.aopanno.User.add(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前...");
joinPoint.proceed();
System.out.println("环绕后...");
}
//最终通知
@After("execution(* ind.deng.spring5.aopanno.User.add(..))")
public void after(){
System.out.println("after...");
}
//异常通知
@AfterThrowing("execution(* ind.deng.spring5.aopanno.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing...");
}
}
环绕通知和其他通知有点不一样,需要加ProceedingJoinPoint形参,不然其他通知都输出不了。而且 joinPoint.proceed()会抛出异常
控制台输出:
3.3.2 配置文件方式实现AOP
了解即可,注解比较常用
<bean id="user" class="ind.deng.spring5.aopanno.User"/>
<bean id="userProxy" class="ind.deng.spring5.aopanno.UserProxy"/>
<!--配置aop-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* ind.deng.spring5.aopanno.User.add(..))"/>
<!--切面。ref为增强类,别忘了加上-->
<aop:aspect ref="userProxy">
<!--通知-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
3.4 切入点表达式的重用
抽取公共切入点表达式
@Pointcut("execution(* ind.deng.spring5.aopanno.User.add(..))")
public void pointDemo() {
}
@Before("pointDemo()")
public void before() {
System.out.println("before...");
}
多个增强类对同一个方法增强,可以在类上加@Order(number)设置优先级,数字越小优先级越高,取值是自然数
3.5 完全注解开发
在配置类上加上以下注解。proxyTargetClass默认为false
@EnableAspectJAutoProxy(proxyTargetClass = true)
也是了解即可
4.1 JDBC Template
JDBC Template是spring对JDBC的封装
先引入新的一些依赖,方便之后使用
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
4.2 配置DataSource
常用连接池:c3p0,Druid。用Druid演示一下
先引入依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
注入bean:先使用固定写法写死
<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
一般将连接数据库的这些固定不变的value值写到配置文件中
jdbc.properties:
prop.driverClass=com.mysql.cj.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/test
prop.userName=root
prop.password=root
为了spring核心配置文件能载入properties配置文件,需要引入context命名空间,并用下面这种方式配置bean:
<context:property-placeholder location="classpath: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="username" value="${prop.userName}"/>
<property name="password" value="${prop.password}"/>
</bean>
4.3 配置JDBCTemplate,注入DataSource
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="datasource"/>
</bean>
4.4 使用JDBCTemplate
创建service类和dao类,service类注入dao对象,dao类注入JDBCTemplate对象
dao实现类:
@Repository
public class BookDaoImpl implements BookDao{
@Resource
private JdbcTemplate jdbcTemplate;
}
service类:
@Service
public class BookService {
@Resource
private BookDao bookDao;
}
在dao的实现类里对数据库进行访问:
@Repository
public class BookDaoImpl implements BookDao{
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public void addBook(Book book) {
String sql = "insert into book values(?,?,?)";
Object[] args = {book.getId(),book.getName(),book.getPrice()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
}
测试
@Test
public void test01(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
BookService bookService = context.getBean("bookService",BookService.class);
Book book = new Book();
book.setId(2);
book.setName("英语");
book.setPrice(20.0);
bookService.addBook(book);
}
控制台
数据库
可以将id字段设为自增,每次插入不用插入id值
更新操作:
@Override
public void updateBook(Book book ,Integer id) {
String sql="update book set name=?,price=? where id=?";
Object[] args = {book.getName(),book.getPrice(),id};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
删除操作:
@Override
public void deleteBook(Integer id) {
String sql="delete from book where id=?";
int update = jdbcTemplate.update(sql, id);
System.out.println(update);
}
通过编号查询(或其他条件)queryForObject 返回值是对象
queryForObject()第一个和第三个参数是老相识了,第二个参数是RowMapper接口,而jdbctemplate已经帮我们实现了该接口,即BeanPropertyRowMapper,new出来这个实现类,泛型是查询结果的类型,参数是查询结果的类.class
@Override
public Book queryBook(Integer id) {
String sql= "select * from book where id=?";
Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
return book;
}
查询全部 query 返回值是List集合
@Override
public List<Book> queryAll() {
String sql="select * from book";
List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
return bookList;
}
批量增加
@Override
public void batchAdd(List<Object[]> args) {
String sql="insert book (name,price) values(?,?)";
int[] ints = jdbcTemplate.batchUpdate(sql, args);
System.out.println(Arrays.toString(ints));
}
测试代码
List<Object[]> args = new ArrayList<>();
Object[] o1 = { "高等数学", 40.0};
Object[] o2 = { "线性代数", 20.0};
Object[] o3 = { "概率论", 29.8};
args.add(o1);
args.add(o2);
args.add(o3);
bookService.batchAdd(args);
批量修改和批量删除大同小异,不再赘述
5.1 处理事务
银行转账案例环境搭建(只贴必要代码)
dao
@Repository
public class AccountDaoImpl implements AccountDao{
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public void addMoney() {
String sql = "update account set money=money+? where name=?";
jdbcTemplate.update(sql,100,"张三");
}
@Override
public void reduceMoney() {
String sql = "update account set money=money-? where name=?";
jdbcTemplate.update(sql,100,"李四");
}
}
service
@Service
public class AccountService {
@Resource
private AccountDao account;
public void handleAccount(){
account.reduceMoney();
account.addMoney();
}
}
事务管理分为编程式事务管理和声明式事务管理,spring采用后者,底层使用了AOP
现在配置文件中引入tx命名空间,并创建事务管理器,开启事务注解
<!--创建事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="datasource"/>
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
在service类上或方法上加@Transactional注解。
这样,service方法就会正常执行,出现异常会回滚
5.2 @Transactional的几个参数
propagation:事务传播行为
事务传播行为指事务方法(增删改等修改数据的方法)间调用时,事务是如何管理的
如A方法有事务,B方法没事务,如果A调用B,事务是怎么处理的
七种事务传播行为:
REQUIRED(默认):A有事务,B没事务,A调B,B用A事务;A没事务,B没事务,A调B,B新建事务
REQUIRED_NEW:无论A有无事务,A调B,B都新建事务,其他运行中的事务会被挂起
这两个用的较多。其他传播行为不再一一列举,去学数据库理论去
isolation:隔离级别
三个读问题:脏读、不可重复读、幻读
脏读:一个未提交事务读到另一个未提交事务的数据
不可重复读:读两次读到的数据不一致(别人修改数据并提交造成的)
幻读:读两次读到的表不一致(行数变化)
通过设置隔离级别解决上面的问题:(从前到后,隔离级别越来越高,解决问题越来越多,并发性越来越小)
READ_UNCOMMITTED读未提交。 没解决问题
READ_COMMITTED读已提交。 解决脏读
REPEATABLE_READ可重复读(默认) 解决脏读和不可重复读
SERIALIZABLE序列化 全部解决
timeout:超时时间
事务需要在一定时间内提交,超时则回滚,以秒为单位,默认值-1即不会超时
readOnly:是否只读
默认false。设为true则禁止事务增删改
rollBackFor
设置出现哪些异常会回滚
noRollBackFor
设置出现哪些异常不会回滚
注:基于配置文件的事务控制跳过,用AOP比较繁琐,平时一般用注解。纯注解方式声明事务也跳过了
6.1 Spring新特性
基于java8
自带了通用的日志功能
结合log4j2一起使用(不再支持版本1),引入依赖
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.15.0</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="DEBUG">
<!--先定义所有的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>
支持@Nullable注解
@Nullable可以加在方法上(表示返回值可以为空)、属性上(表示属性可以为空)、参数上(表示参数可以为空)
支持函数式风格
整合Junit5
先看如何整合junit4
引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.9</version>
</dependency>
用注解测试,方便省事。@RunWith(SpringJUnit4ClassRunner.class)写法固定。别忘了前面的Spring,没加的话报空指针
@ContextConfiguration(“classpath:bean.xml”)加载配置文件。需要测试的service对象也可以自动注入
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:bean.xml")
public class JUnitTest {
@Resource
private AccountService account;
@Test
public void test01(){
account.handleAccount();
}
}
整合JUnit5
引入依赖
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
第一个注解换了:
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean.xml")
public class JUnitTest {
@Resource
private AccountService account;
@Test
public void test01(){
account.handleAccount();
}
}
可以两个注解二合一,更方便:
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class JUnitTest {
@Resource
private AccountService account;
@Test
public void test01(){
account.handleAccount();
}
}
6.2 Spring WebFlux
spring5新添加的模块,用于web开发,功能与springmvc类似。异步(调用者发出请求后不是干等而是做其他事情)非阻塞(被吊用着收到请求立即反馈再开始任务),响应式编程,servlet3.1及以上
核心是基于Reactor的相关API实现的
特点:
非阻塞式:有限资源下提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程(RP)
函数式编程:基于java8,通过函数式编程实现路由请求
WebFlux可以单开一门课了,不再赘述。