Spring 有两个核心部分:IoC 和AOP
IoC:控制反转,把创建对象过程交给 Spring 进行管理
AOP:面向切面,不修改源代码进行功能增强
IoC
IoC全称Inversion of Control,直译为控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理 ,又称为依赖注入(DI:Dependency Injection),它解决了一个最主要的问题:将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。
Xml方式
简单例子:
1 2 3 4 5 public class User { public void show () { System.out.println("------show------" ); } }
spring config 的 bean1.xml:
1 2 3 4 5 6 7 8 9 <?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 ="user" class ="com.kanxz.spring5.User" /> </beans >
最后测试代码:
1 2 3 4 5 6 7 8 @Test public void testUser () { ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml" ); User user = context.getBean(User.class); user.show(); }
基于 xml 方式创建对象
maven项目目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 |-- pom.xml `-- src |-- main | |-- java | | `-- com | | `-- kanxz | | `-- spring5 | | `-- Book.java | `-- resources | `-- bean1.xml `-- test `-- java
bean1.xml为spring config文件
创建对象:
1 <bean id ="book" class ="com.kanxz.spring5.Book" />
基于 xml 方式注入属性
第一种注入方式:使用 set 方法进行注入
创建Book类,有属性bookName
, bookAuthor
,并定义set方法。
在 spring 配置文件(bean1.xml)配置对象创建,配置属性注入
1 2 3 4 5 6 7 8 9 <bean id ="book" class ="com.kanxz.spring5.Book" > <property name ="bookName" value ="C++" /> <property name ="bookAuthor" value ="Java" /> </bean >
第二种注入方式:使用有参数构造进行注入
定义有含参构造方法的类
在 spring 配置文件中进行配置:
1 2 3 4 5 <bean id ="order" class ="com.kanxz.spring5.Order" > <constructor-arg name ="orderName" value ="电脑" /> <constructor-arg name ="orderAddress" value ="China" /> </bean >
p 名称空间注入
使用 p 名称空间注入,可以简化基于 xml 配置方式
第一步 添加 p 名称空间在配置文件中
1 2 3 4 <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 标签里面进行操作
1 2 <bean id ="book1" class ="com.kanxz.spring5.Book" p:bookName ="C++" p:bookAuthor ="李四" />
xml 注入其他类型属性
1 2 3 4 5 6 7 8 9 10 11 12 13 <bean id ="book" class ="com.kanxz.spring5.Book" > <property name ="bookName" > <value > <![CDATA[<<Java>>]]></value > </property > <property name ="bookAddress" > <null /> </property > </bean >
注入属性-外部 bean
(1)创建两个类 service 类和 dao 类
(2)在 service 调用 dao 里面的方法
(3)在 spring 配置文件中进行配置
1 2 3 4 5 6 7 <bean id ="userService" class ="com.kanxz.spring5.service.UserService" > <property name ="userDao" ref ="userDaoImpl" /> </bean > <bean id ="userDaoImpl" class ="com.kanxz.spring5.dao.UserDaoImpl" />
注入属性-内部 bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Dept { private String dname; public void setDname (String dname) { this .dname = dname; } } public class Emp { private String ename; private String gender; private Dept dept; public void setDept (Dept dept) { this .dept = dept; } public void setEname (String ename) { this .ename = ename; } public void setGender (String gender) { this .gender = gender; } }
1 2 3 4 5 6 7 8 9 10 11 12 <bean id ="emp" class ="com.kanxz.spring5.bean.Emp" > <property name ="ename" value ="lucy" > </property > <property name ="gender" value ="女" > </property > <property name ="dept" > <bean id ="dept" class ="com.kanxz.spring5.bean.Dept" > <property name ="dname" value ="安保部" > </property > </bean > </property > </bean >
注入属性-级联赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 <bean id ="emp" class ="com.kanxz.spring5.bean.Emp" > <property name ="empName" value ="李四" /> <property name ="empGender" value ="女" /> <property name ="dept" ref ="dept" /> <property name ="dept.deptName" value ="财务部" /> </bean > <bean id ="dept" class ="com.kanxz.spring5.bean.Dept" > <property name ="deptName" value ="技术部" /> </bean >
xml 注入集合属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <bean id ="stu" class ="com.kanxz.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 ="JAVA" value ="java" > </entry > <entry key ="PHP" value ="php" > </entry > </map > </property > <property name ="sets" > <set > <value > MySQL</value > <value > Redis</value > </set > </property > </bean >
在集合里面设置对象类型值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <bean id ="course1" class ="com.kanxz.spring5.collectiontype.Course" > <property name ="cname" value ="Spring5 框架" > </property > </bean > <bean id ="course2" class ="com.kanxz.spring5.collectiontype.Course" > <property name ="cname" value ="MyBatis 框架" > </property > </bean > <property name ="courseList" > <list > <ref bean ="course1" > </ref > <ref bean ="course2" > </ref > </list > </property >
基于注解方式
1、什么是注解
(1)注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值…)
(2)使用注解,注解作用在类上面,方法上面,属性上面
(3)使用注解目的:简化 xml 配置
2、Spring 针对 Bean 管理中创建对象提供注解
(1)@Component
(2)@Service
(3)@Controller
(4)@Repository
上面四个注解功能是一样的,都可以用来创建bean 实例
基于注解方式实现对象创建
1 2 3 4 5 6 7 8 |-- com `-- kanxz `-- spring5 |-- dao | |-- UserDao.java | `-- UserDaoImpl.java `-- service `-- UserService.java
开启组件扫描:
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:context ="http://www.springframework.org/schema/context" 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" > <context:component-scan base-package ="com.kanxz.spring5" /> </beans >
创建类,在类上面添加创建对象注解:
1 2 3 4 5 6 @Component(value = "userService") public class UserService { public void add () { System.out.println("service add......." ); } }
基于注解方式实现属性注入
@Autowired:根据属性类型进行自动装配
第一步 把 service 和 dao 对象创建,在 service 和 dao 类添加创建对象注解
第二步 在 service 注入 dao 对象,在 service 类添加 dao 类型属性,在属性上面使用注解
1 2 3 4 5 6 7 8 9 10 11 12 13 @Service public class UserService { @Autowired private UserDao userDao; public void add () { System.out.println("service add......." ); userDao.add(); } }
@Qualifier:根据名称进行注入
这个@Qualifier 注解的使用,和上面@Autowired 一起使用
1 2 3 4 5 6 @Autowired @Qualifier(value = "userDaoImpl") private UserDao userDao;
@Resource:可以根据类型注入,可以根据名称注入
1 2 3 @Resource(name = "userDaoImpl1") private UserDao userDao;
@Value:注入普通类型属性
1 2 @Value(value = "abc") private String name;
完全注解方式
1 2 3 4 5 6 7 8 9 |-- com `-- kanxz `-- spring5 `-- AppConfig.java |-- dao | |-- UserDao.java | `-- UserDaoImpl.java `-- service `-- UserService.java
创建配置类,替代xml配置文件
1 2 3 4 5 @Configuration @ComponentScan public class AppConfig { ... }
AppConfig
标注了@Configuration
,表示它是一个配置类
AppConfig
标注了@ComponentScan
,它告诉容器,自动搜索当前类所在的包以及子包,把所有标注为@Component
的Bean自动创建出来,并根据@Autowired
进行装配。
编写测试类
1 2 3 4 5 6 7 8 @Test public void testService2 () { ApplicationContext context = new AnnotationConfigApplicationContext(APPConfig.class); UserService userService = context.getBean(UserService.class); System.out.println(userService); userService.add(); }
使用Annotation配合自动扫描能大幅简化Spring的配置,我们只需要保证:
每个Bean被标注为@Component
并正确使用@Autowired
注入;
配置类被标注为@Configuration
和@ComponentScan
;
所有Bean均在指定包以及子包内。
使用@ComponentScan
非常方便,但是,我们也要特别注意包的层次结构。通常来说,启动配置AppConfig
位于自定义的顶层包,其他Bean按类别放入子包。
使用Resource
在Java程序中,我们经常会读取配置文件、资源文件等。使用Spring容器时,我们也可以把“文件”注入进来,方便程序读取。
例如,AppService需要读取logo.txt
这个文件,通常情况下,我们需要写很多繁琐的代码,主要是为了定位文件,打开InputStream。
Spring提供了一个org.springframework.core.io.Resource
(注意不是javax.annotation.Resource
),它可以像String
、int
一样使用@Value
注入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component public class AppService { @Value("classpath:/logo.txt") private Resource resource; private String logo; @PostConstruct public void init () throws IOException { try (var reader = new BufferedReader( new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) { this .logo = reader.lines().collect(Collectors.joining("\n" )); } } }
注入Resource
最常用的方式是通过classpath,即类似classpath:/logo.txt
表示在classpath中搜索logo.txt
文件,然后,我们直接调用Resource.getInputStream()
就可以获取到输入流,避免了自己搜索文件的代码。
目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 |-- pom.xml `-- src |-- main | |-- java | | `-- com | | `-- kanxz | | `-- spring5 | | |-- AppConfig.java | | `-- AppService.java | `-- resources | `-- logo.txt `-- test `-- java
注入配置
在开发应用程序时,经常需要读取配置文件。最常用的配置方法是以key=value
的形式写在.properties
文件中。
要读取配置文件,我们可以使用Resource
来读取位于classpath下的一个app.properties
文件。但是,这样仍然比较繁琐。
Spring容器还提供了一个更简单的@PropertySource
来自动读取配置文件。我们只需要在@Configuration
配置类上再添加一个注解:
1 2 3 4 5 6 7 8 9 10 11 @ComponentScan @Configuration @PropertySource("/app.properties") public class AppConfig { @Value("${app.username}") private String username; @Value("${app.password}") private String password; }
1 2 app.username =Tom app.password =123456
Spring容器看到@PropertySource("/app.properties")
注解后,自动读取这个配置文件,然后,我们使用@Value
正常注入。
注意注入的字符串语法,它的格式如下:
"${app.username}"
表示读取key为app.username
的value,如果key不存在,启动将报错;
"${app.username:Mary}"
表示读取key为app.username
的value,但如果key不存在,就使用默认值Mary
。
另一种注入配置的方式是先通过一个简单的JavaBean持有所有的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @ComponentScan @Configuration @PropertySource("/app.properties") public class AppConfig { @Value("${app.username}") private String username; @Value("${app.password}") private String password; public String getUsername () { return username; } public String getPassword () { return password; } }
然后,在需要读取的地方,使用#{appConfig.username}
注入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component public class AppService { @Value("#{appConfig.username}") private String username; @Value("#{appConfig.password}") private String password; public void show () { System.out.println(username); System.out.println(password); } }
注意观察#{}
这种注入语法,它和${key}
不同的是,#{}
表示从JavaBean读取属性。"#{appConfig.username}"
的意思是,从名称为appConfig
的Bean读取username
属性,即调用getUsername()
方法。一个Class名为AppConfig
的Bean,它在Spring容器中的默认名称就是appConfig
,除非用@Qualifier
指定了名称。
使用一个独立的JavaBean持有所有属性,然后在其他Bean中以#{bean.property}
注入的好处是,多个Bean都可以引用同一个Bean的某个属性。例如,如果AppConfig
决定从数据库中读取相关配置项,那么AppService
注入的@Value("#{appConfig.username}")
仍然可以不修改正常运行。
目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 `-- src |-- main | |-- java | | `-- com | | `-- kanxz | | `-- spring5 | | |-- AppConfig.java | | `-- AppService.java | `-- resources | `-- app.properties `-- test `-- java `-- AppTest.java
AOP
AOP是Aspect Oriented Programming,即面向切面编程。
利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
AOP 底层使用动态代理,有两种情况动态代理
第一种 有接口情况,使用 JDK 动态代理,创建接口实现类代理对象,增强类的方法
第二种 没有接口情况,使用 CGLIB 动态代理,创建子类的代理对象,增强类的方法
在AOP编程中,我们经常会遇到下面的概念:
Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点;
Joinpoint:连接点,即定义在应用程序流程的何处插入切面的执行;
Pointcut:切入点,即一组连接点的集合;
Advice:通知(增强),指特定连接点上执行的动作;
前置通知(Before):在目标方法被调用之前调用通知功能。
后置通知(After):在目标方法完成之后调用通知,无论该方法是否发生异常。
后置返回通知(After-returning):在目标方法成功执行之后调用通知。
后置异常通知(After-throwing):在目标方法抛出异常后调用通知。
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
Introduction:引介,指为一个已有的Java对象动态地增加新的接口;
Weaving:织入,指将切面整合到程序的执行流程中;
Interceptor:拦截器,是一种实现增强的方式;
Target Object:目标对象,即真正执行业务的核心逻辑对象;
AOP Proxy:AOP代理,是客户端持有的增强后的对象引用。
装配AOP
看一个例子来更快了解:
首先,我们通过Maven引入Spring对AOP的支持:
1 2 3 4 5 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aspects</artifactId > <version > 5.2.12.RELEASE</version > </dependency >
上述依赖会自动引入AspectJ,使用AspectJ实现AOP比较方便,因为它的定义比较简单。
基本目录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |-- pom.xml `-- src |-- main | |-- java | | `-- com | | `-- kanxz | | `-- spring5 | | |-- AppConfig.java | | |-- LoginAspect.java | | `-- service | | |-- MailService.java | | |-- User.java | | `-- UserService.java | `-- resources `-- test `-- java
其中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Component public class MailService { public String getTime () { return LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); } public void sendLoginMail (User user) { System.err.printf("Hi, %s! You are login at %s\n" , user.getName(), getTime()); } public void sendRegistrationMail (User user) { System.err.printf("Hello %s!\n" , user.getName()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Component public class UserService { private final MailService mailService; public UserService (MailService mailService) { this .mailService = mailService; } private List<User> users = new ArrayList<>(List.of( new User(1 , "Tom@example.com" , "pd" , "Tom" ), new User(2 , "Bob@example.com" , "pd" , "Bob" ), new User(3 , "Alice@example.com" , "pd" , "Alice" ) )); public User login (String email, String pd) { for (User user : users) { if (user.getEmail().equalsIgnoreCase(email) && user.getPassword().equalsIgnoreCase(pd)) { mailService.sendLoginMail(user); return user; } } throw new RuntimeException("login failed" ); } public User getUser (long id) { return users.stream().filter(user -> user.getId() == id).findFirst().orElseThrow(); } public User register (String email, String pd, String name) { users.forEach(user -> { if (user.getEmail().equalsIgnoreCase(email)) { throw new RuntimeException("user already exists!" ); } }); User user = new User(users.stream().mapToLong(User::getId).max().getAsLong() + 1 , email, pd, name); users.add(user); mailService.sendRegistrationMail(user); return user; } }
配置类:
1 2 3 4 5 6 7 8 9 10 11 12 13 @ComponentScan @Configuration public class AppConfig { public static void main (String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); UserService userService = context.getBean(UserService.class); User user = userService.register("libai@gmail.com" , "libai" , "liBai" ); System.out.println(user); userService.login(user.getEmail(), user.getPassword()); } }
此时运行的结果是:
Hello liBai!
User{id=‘4’, email=‘libai@gmail.com’, password=‘libai’, name=‘liBai’}
Hi, liBai! You are login at 2020-12-22T16:52:05.487303
现在,我们准备给UserService
的每个业务方法执行前添加日志,给MailService
的每个业务方法执行前后添加日志:(需要给AppConfig
加上@EnableAspectJAutoProxy
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Component @Aspect public class LoginAspect { @Before("execution(public * com.kanxz.spring5.service.UserService.*(..))") public void doAccessCheck () { System.err.println("[Before] do access check..." ); } @Around("execution(public * com.kanxz.spring5.service.UserService.*(..))") public Object doLogging (ProceedingJoinPoint point) throws Throwable { System.err.println("[Around] start; " + point.getSignature()); Object proceed = point.proceed(); System.err.println("[Around] end; " + point.getSignature()); return proceed; } }
此时运行结果:
[Around] start; User com.kanxz.spring5.service.UserService.register(String,String,String)
[Before] do access check…
Hello liBai!
[Around] end; User com.kanxz.spring5.service.UserService.register(String,String,String)
User{id=‘4’, email=‘libai@gmail.com’, password=‘libai’, name=‘liBai’}
[Around] start; User com.kanxz.spring5.service.UserService.login(String,String)
[Before] do access check…
Hi, liBai! You are login at 2020-12-22T17:01:03.7016413
[Around] end; User com.kanxz.spring5.service.UserService.login(String,String)
观察doAccessCheck()
方法,我们定义了一个@Before
注解,后面的字符串是告诉AspectJ应该在何处执行该方法,这里写的意思是:执行UserService
的每个public
方法前执行doAccessCheck()
代码。
再观察doLogging()
方法,我们定义了一个@Around
注解,它和@Before
不同,@Around
可以决定是否执行目标方法,因此,我们在doLogging()
内部先打印日志,再调用方法,最后打印日志后返回结果。
在LoggingAspect
类的声明处,除了用@Component
表示它本身也是一个Bean外,我们再加上@Aspect
注解,表示它的@Before
标注的方法需要注入到UserService
的每个public
方法执行前,@Around
标注的方法需要注入到MailService
的每个public
方法执行前后。
再来看看execution
语法:
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
其中:返回类型模式、方法名模式、参数模式是必选项。
execution(public * com.kanxz.spring5.service.UserService.*(..))
public
:修饰符模式
*
:所有返回类型
com.kanxz.spring5.service.UserService.*
:表示UserService
类中的所有方法
(..)
代表任意参数
使用注解装配AOP
我们以一个实际例子演示如何使用注解实现AOP装配。
目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |-- pom.xml `-- src |-- main | |-- java | | `-- com | | `-- kanxz | | `-- spring5 | | |-- AppConfig.java | | |-- metrics | | | |-- MetricAspect.java | | | `-- MetricTime.java | | `-- service | | |-- MailService.java | | |-- User.java | | `-- UserService.java | `-- resources `-- test `-- java
为了监控应用程序的性能,我们定义一个性能监控的注解:
1 2 3 4 5 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MetricTime { String value () ; }
Retention注解
@Retention(保留)注解说明这种类型的注解会被保留到某个阶段,有三个值:
RetentionPolicy.SOURCE
—— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
RetentionPolicy.CLASS
—— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
RetentionPolicy.RUNTIME
—— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用
Target注解
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
CONSTRUCTOR:用于描述构造器
FIELD:用于描述域
LOCAL_VARIABLE:用于描述局部变量
METHOD:用于描述方法
PACKAGE:用于描述包
PARAMETER:用于描述参数
TYPE:用于描述类、接口(包括注解类型) 或enum声明
在需要被监控的关键方法上标注该注解:
1 2 3 4 5 6 7 8 9 @Component public class UserService { @MetricTime("register") public User register (String email, String password, String name) { ... } ... }
然后,我们定义MetricAspect
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Aspect @Component public class MetricAspect { @Around("@annotation(metricTime)") public Object metric (ProceedingJoinPoint joinPoint, MetricTime metricTime) throws Throwable { String name = metricTime.value(); long start = System.currentTimeMillis(); try { return joinPoint.proceed(); } finally { long t = System.currentTimeMillis() - start; System.err.println("[Metrics] " + name + ": " + t + "ms" ); } } }
注意metric()
方法标注了@Around("@annotation(metricTime)")
,它的意思是,符合条件的目标方法是带有@MetricTime
注解的方法,因为metric()
方法参数类型是MetricTime
(注意参数名是metricTime
不是MetricTime
),我们通过它获取性能监控的名称。
有了@MetricTime
注解,再配合MetricAspect
,任何Bean,只要方法标注了@MetricTime
注解,就可以自动实现性能监控。
数据库
在前面学习JDBC编程时学到,Java程序使用JDBC接口访问关系数据库的时候,需要以下几步:
创建全局DataSource
实例,表示数据库连接池;
在需要读写数据库的方法内部,按如下步骤访问数据库:
从全局DataSource
实例获取Connection
实例;
通过Connection
实例创建PreparedStatement
实例;
执行SQL语句,如果是查询,则通过ResultSet
读取结果集,如果是修改,则获得int
结果。
在Spring使用JDBC,首先我们通过IoC容器创建并管理一个DataSource
实例,然后,Spring提供了一个JdbcTemplate
,可以方便地让我们操作JDBC,因此,通常情况下,我们会实例化一个JdbcTemplate
。
来看一个例子:
目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |-- pom.xml `-- src |-- main | |-- java | | `-- com | | `-- kanxz | | `-- spring5 | | `-- jdbc | | |-- JDBCConfig.java | | |-- JDBCService.java | | `-- User.java | `-- resources | `-- jdbc.properties `-- test `-- java
首先添加maven依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.2.12.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 5.2.12.RELEASE</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.22</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.2.3</version > </dependency >
在JDBCConfig中,我们需要创建以下几个必须的Bean:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @ComponentScan @Configuration @PropertySource("/jdbc.properties") public class JDBCConfig { @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Value("${jdbc.driverClassName}") private String driverClassName; @Bean DataSource createDataSource () { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setDriverClassName(driverClassName); return dataSource; } @Bean JdbcTemplate createJdbcTemplate (DataSource dataSource) { return new JdbcTemplate(dataSource); } }
其中,配置文件为:
1 2 3 4 jdbc.url =jdbc:mysql:///user_db?serverTimezone=UTC jdbc.username =root jdbc.password =jdbc.driverClassName =com.mysql.cj.jdbc.Driver
新建好数据库user_db,表users,含有id, email, password, name
建立对应的User
类,设置相应的set, get方法
增删改
调用 JdbcTemplate
对象里面 update
方法实现增删改操作:
1 public int update (String sql, @Nullable Object... args)
有两个参数:sql语句,和可变参数(用于填充sql语句)
返回影响的行数
JDBCService
类添加各种方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Component public class JDBCService { JdbcTemplate jdbcTemplate; @Autowired public void setJdbcTemplate (JdbcTemplate jdbcTemplate) { this .jdbcTemplate = jdbcTemplate; } public void addUser (User user) { String sql = "insert into users value (?, ?, ?, ?)" ; int update = jdbcTemplate.update(sql, user.getId(), user.getEmail(), user.getPassword(), user.getName()); System.out.println("添加了" + update + "条数据" ); } public void updateUser (User user) { String sql = "update users set email = ?, password = ?, name = ? where id = ?" ; int update = jdbcTemplate.update(sql, user.getEmail(), user.getPassword(), user.getName(), user.getId()); System.out.println("修改了" + update + "条数据" ); } public void delUser (int id) { String sql = "delete from users where id = ?" ; int update = jdbcTemplate.update(sql, id); System.out.println("删除了" + update + "条数据" ); } }
查询
查询返回值
1 public <T> T queryForObject (String sql, Class<T> requiredType)
有两个参数 :第一个参数:sql 语句, 第二个参数:返回类型 Class
1 2 3 4 5 public int selectCount () { String sql = "select count(*) from users" ; int count = jdbcTemplate.queryForObject(sql, Integer.class); return count; }
查询返回对象
1 public <T> T queryForObject (String sql, RowMapper<T> rowMapper, @Nullable Object... args)
有三个参数 :第一个参数:sql 语句, 第二个参数:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装,第三个参数:sql 语句值
1 2 3 4 5 public User findUserById (int id) { String sql = "select * from users where id = ?" ; User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), id); return user; }
查询返回集合
1 public <T> List<T> query (String sql, RowMapper<T> rowMapper, @Nullable Object... args)
有三个参数 :第一个参数:sql 语句 , 第二个参数:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装 ,第三个参数:sql 语句值
1 2 3 4 5 public List<User> getAll () { String sql = "select * from users" ; List<User> lists = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class)); return lists; }
Spring5 整合 JUnit5
以上面数据库操作的测试为例:
原操作:
1 2 3 4 5 6 7 @Test public void testAdd () { ApplicationContext context = new AnnotationConfigApplicationContext(JDBCConfig.class); JDBCService jdbcService = context.getBean(this .jdbcService.getClass()); User user = new User(3 , "Mary@gmail.com" , "pd" , "Mary" ); jdbcService.addUser(user); }
现引入依赖:
1 2 3 4 5 6 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-test</artifactId > <version > 5.2.12.RELEASE</version > <scope > test</scope > </dependency >
可以得到简化的写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = JDBCConfig.class) public class JDBCTest { @Autowired private JDBCService jdbcService; @Test public void testAdd () { User user = new User(3 , "Mary@gmail.com" , "pd" , "Mary" ); jdbcService.addUser(user); } @Test public void testUpdate () { User user = new User(3 , "Mary@gmail.com" , "password" , "Mary" ); jdbcService.updateUser(user); } @Test public void testDel () { jdbcService.delUser(3 ); } @Test public void testSelectCount () { System.out.println(jdbcService.selectCount()); } @Test public void testFindUserById () { User user = jdbcService.findUserById(2 ); System.out.println(user); } @Test public void testGetAll () { List<User> users = jdbcService.getAll(); users.forEach(System.out::println); } }
学习自: