一、Spring
1.1 概述
Spring是分层的Java SE/EE应用轻量级开源框架,以IOC(Inverse Of Control:控制反转)和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还可以整合众多第三方框架。
1.2 IOC
我们平时开发中如果创建对象需要去new 一个对象,这个动作是我们主动去创建的。
Spring中的IOC就是去帮我们解决这个问题,不需要我们去创建对象,把创建对象这个权利交给spring,由spring帮我们创建,我们需要用的时候直接通过spring容器来获取,原理如下图:
明确IOC的作用:削减计算机程序中的耦合(解除代码中的依赖关系)
IOC的实现
引入spring的相关jar包,创建一个xml文件,导入spring约束,然后配置javabean
然后就可以测试一下是否配置成功,先获取Spring容器,然后从容器中获取相应的对象
DI:依赖注入,如何理解呢?A类需要调用B类的某个方法完成功能,则把B类对象传入A类的这个过程我们称为依赖注入。(也就是控制层引入Service层用到的@Autowired注解)。
IOC和DI主要解决的问题:
IOC:主要解决bean的创建问题,原来的bean是在程序中硬编码new出来,现在交给spring容器来进行创建
DI:bean与 bean之间的依赖注入,在spring容器里来通过配置的方式 解决依赖的动态注入问题
注意:spring管理组件之前的依赖注入关系的前提是:关联的组件必须由 spring来进行创建并放在spring容器中。
基于注解实现IOC的配置
他和上面几张图的xml配置所实现的功能是一样的,不过一般还是用注解的多(个人看法- -)
@Component(value=“userService”):作用于类名上面,用于代替xml配置中的bean标签。value就是此类的id,没有value默认该类名第一个字母小写即为它的id
由@Component衍生出的注解:
@Controller 作用于控制层
@Service 作用于业务层
@Repository 作用于持久层
这三个注解的作用@Component一样都是用来创建bean对象,一般来说开发中用到的都是衍生出来的这3个注解。
DI注解:
@Autowired 这个注解用于属性或方法上,会从spring容器中去找对应的属性,若有一个符合的类型就取出,若有多个就根据名称取,若没有对应的名称就报错,因为找到了多个不知道注入哪一个。如果找不到相应的类型也会报错,因为找不到。
@Qualifier 配合@Autowired使用,将多个符合条件的类型,按照名称注入
@Resource(name=“aaa”) 根据bean的id来注入,相当于上面两个注解放一起的作用
@Value 用来指定基本数据类型和String类型的值注入
关于DI这几个注解中,其实开发中也就用到个@Autowired,其他的一般都用不上,因为很少会对多个类命名相同的名字吧
@Scope 作用于类名上,指定bean的作用范围,单例(singleton)or多例(prototype)
单例:IOC容器启动时就会创建该对象,以后每次获取都是从容器中拿这同一个对象。(优点:没必要每个请求都新建一个对象,这样子既浪费CPU又浪费内存; 缺点:当类中有全局变量且对对象状态值改变时,发生并发请求时可能会出现线程安全问题。)
多例:IOC容器启动时不会创建该对象,当你需要的时候从容器中取的时候才会创建对象,即每次请求用到的都是一个新的对象。(优点和缺点与单例相对)
1.3 AOP
AOP:面向切面编程,通过预编译和运行期动态代理实现程序功能的统一维护的一种技术。简单点说,它就是把我们程序中重复的代码抽离出来,需要执行的时候,使用动态代理技术在不修改源码的基础上,对我们已有的方法进行增强。
我们编程中常用到AOP的就是安全检验、日志操作、事务操作等,如下图来理解AOP
这张图看起来很简单,但是也很形象的展示了AOP的作用。上面横线的就是切面,竖线就是我们的代码,在每个代码块执行过程中其实有很多东西是我们公用的功能,比如安全检验、日志操作、事务控制等这些,如果这些功能每写一个代码都要加入到进去显然很不合理,首先代码冗余性很高,且会代码会非常复杂。
AOP的作用就是把这些复杂但又必须要有的功能代码单独抽离出来,通过java动态代理的逻辑原理对这些功能进行管理,在代码执行的过程中动态的把这些功能放入代码执行的过程中。
AOP的相关术语:
A. 通知(Advice):就是上面说到的你想增强的功能,就是那些重复但又必须有的公共的功能,例如安全、事务、日志等。(就是这些功能写到一个类里,那这个类就是一个通知类)
B. 连接点(joinPoint): Spring允许你加通知的地方,可以在每个方法的前后,或抛异常的地方都可以是连接点,也就是和方法有关的前前后后都可以是连接点。(连接点只是为了让你更好的理解下面的切点,其实没它啥事)
C. 切入点(pointCut): 和上面的连接点类似,连接点是你可以加通知的地方,但是你在开发中肯定不是所有的连接点都要去加通知吧,那么切入点的意思就是定义你需要在哪个连接点去加通知,简单来说切入点就是来筛选要加通知的连接点。
D. 切面(Aspect): 切面=通知+切入点 ,通知和切入点组合起来就是一个切面,这切面就包括了我们要增强的方法(通知),这些方法我放到哪里(切入点)。
E. 目标对象(target):被代理的对象,就是我们的通知要去增加的那个对象。
F. 代理对象(proxyObject ):目标对象被增强后,就成了代理对象。
G. 织入(weaving): weaving 指的将功能增强的代码应用于目标对象的方法中的过程称之为织入
Advice通知的类型:
A ,前置增强:目标对象的方法执行之前执行的代码
B,后置增强:目标对象的方法执行之后执行的代码
C,异常通知:目标对象的方法执行时发生异常所进行的代码处理
D,最终通知:目标对象的方法无论执行成功与否都会执行的代码
E,环绕通知:整个围绕着目标对象方法执行的过程称之为环绕通知
Spring中AOP的配置:
1,配置目标对象(target)
<!-- 配置srping的Ioc,把service对象配置进来 目标对象-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
2.配置通知类(Advice)
<!-- 配置Logger类 通知类-->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
3.aop的配置
<!--配置AOP-->
<aop:config>
<!--配置切面 id:切面标识 ref=指定引用的通知类-->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置通知的类型,并且建立通知方法和切入点方法的关联 -->
<aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
<!-- <aop:after-returning method="printMesg" pointcut="execution(* com.itheima.service..*.*(..))"></aop:after-returning>-->在这里插入代码片
</aop:aspect>
</aop:config>
<!--和事务通知相关的配置-->
<aop:config>
<aop:pointcut id="serviceOperation" expression="execution(* com..*Service.*(..))" />
<aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="search*" read-only="true"></tx:method>
<tx:method name="save*" isolation="DEFAULT" read-only="false" propagation="REQUIRED" rollback-for="Exception"></tx:method>
</tx:attributes>
</tx:advice>
pointCut:切点表达式,切点表达式要能被解析,必须引入aspectjweaver.jar包
基于注解的AOP配置
@Before(“切点表达式”): 前置通知
@AfterReturning(“切点表达式”): 后置通知
@AfterThrowing(“切点表达式”):异常通知
@After(““切点表达式”):最终通知
@Around(““切点表达式”):环绕通知
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){}
@Pointcut("execution(* com.itheima.service2.impl.*.*(..))")
private void pt2(){}
@Before("pt1()|pt2()")
Spring的声明式事务处理–重点
普通的事务处理:基于编码来实现,且这个事务管理器是需要我们自己来写的。
声明式事务处理:Spring给我们提供的事务管理器,我们只需要写一些配置就可以将此事务注入到目标对象target中。
基于的API
1,PlatformTransactionManager: 平台事物管理器,接口
DataSourceTransactionManager:主要基于jdbc或 mybatis框架实现的持久化操作
HibernateTransactionManager:主要基于hibernate框架实现的持久化操作
JPATransactionManager:主要基于 JPA框架实现的持久化操作。
2,TransactionDefinition:事务定义
A,事务隔离级别:默认是所选用的数据库的隔离级别
B,传播行为:当一个业务方法包含 多个service调用时,
C,超时时间:默认值为-1,表示没有时间限制。
D,只读事务:如果是只读事务,表示当前事务只能执行查询操作,只有在非只读事务的情况下才能执行增删改。
3,TransactionStatus: 事务状态 ,提供的方法主要帮我们获得事物的执行状态
下面说一下spring中声明式事务的配置,首先要导入spring-tx.jar包
基于xml的配置
1.配置目标对象
<!-- 配置业务层-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
2.配置事务管理器: 就是配置spring提供的那个事务管理器DataSourceTransactionManager
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
前面两部其实都是IOC的配置,配置相应的bean, 和类上面加@Compent/@Controller/@Service等注解效果是一样的。
3.配置事务通知(也就是配置通知类:Advice)
<!-- 配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
- 配置aop
<!-- 配置aop-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--建立切入点表达式和事务通知的对应关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
以上就是基于xml的声明式事务的配置方式,其实和上面的aop的普通配置一样,只不过这个相当于通知Advice采用Spring给我们提供的事务管理器,不用我们自己写通知了~
基于注解的方式配置声明式事务:
@Transactional
@Transactional(propagation= Propagation.REQUIRED,readOnly=false)
一般情况下我们建议使用基于XML配置的方式来使用声明式事物。
至此,Spring中的两大特性IOC和AOP就简单介绍完毕了~
二、SpringMVC
1. 三层架构
开发架构一般基于C/S架构(客户端/服务器)或者B/S架构(浏览器/服务器),在javaEE的开发中几乎都是基于B/S架构,而B/S架构中系统的标准三层架构为:表现层、业务层、持久层。
2.MVC模型
Model(模型):通常指的就是我们的数据模型。作用一般情况下用于封装数据。
View(视图):通常指的就是我们的 jsp 或者 html。作用一般就是展示数据的。
Controller(控制器):是应用程序中处理用户交互的部分。作用一般就是处理程序逻辑的。
3.SpringMVC的概念
SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,它通过一套注解,让一个简单的 Java 类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful 编程风格的请求。
下面由一张图,了解springMVC的位置及作用
4. 下面来看一下springMVC的执行流程原理
5. 上图中涉及到的组件:
DispatcherServlet:前端控制器,它是整个流程的控制中心,由它调用其他组件来处理用户的请求,它的存在有效的降低了组件之间的耦合性。
HandlerMapping:处理器映射器,根据用户的请求地址,去找到对应的Handler(处理器)。
Handler:处理器,就是开发中编写的具体的业务控制器(就是Controller)
HandlerAdapter:处理器适配器,它主要的作用就是对Handler(处理器)进行执行,为什么Handler不直接执行而需要HandlerAdapter来执行呢?这就是适配器模式,因为有很多种控制器,一种是加@Controller注解的,还有就是写一个servlet也可以当做控制器。由上图可以看到,Handler执行后是要返回数据给DispatcherServlet的,如果我们不用HandlerAdapter处理Handler,那么在DispatcherServlet里就要进行很多if else if else 的判断去判断返回过来的是由哪个Handler过来的,而使用HandlerAdapter执行Handler后,不管是什么Hadnler,通过适配后,我们都可以返回给DispatcherServlet一个统一的规范接口(这个接口就像USB接口,而Handler就像是安卓、苹果、type-c等各种接口)。
ViewResolver:视图解析器,就是生成一个View视图对象,再把后台返回的数据进行渲染处理,通过页面返回给用户。
View:就是返回给用户的视图,例如jsp
SpringMVC的配置(这些配置比较麻烦,且没必要去死记这些配置,现在开发基本都是SpringBoot了,这边就简单过一下吧)
-
根据坐标配置依赖,引入相应jar包
-
配置核心的控制器(配置DispatcherServlet)
在web.xml中配置文件的核心控制器
<!-- SpringMVC的核心控制器 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet- class>
<!-- 配置Servlet的初始化参数,读取springmvc的配置文件,创建spring容器 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 配置servlet启动时加载对象 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
- 编写springmvc.xml的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" 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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置spring开启注解mvc的支持
<mvc:annotation-driven></mvc:annotation-driven>
-->
</beans>
SpringMVC中的注解:
@RequestMapping(value="/test", method=RequestMethod.POST, params={“moeny!100”}):可以作用在类、方法上,作用在类上表示请求URL的一级访问目录,不写默认就是应用根目录;作用在方法上,表示URL的二级访问目录。
value:值,可省略。method,请求方法类型。params, 参数money不能是100.
以上这几个属性是与 (&&) 的关系
@RequestParam:作用在方法形参上,required表示请求是否必须提供此参数
public String useRequestParam(@RequestParam("name")String username,
@RequestParam(value="age",required=false)Integer age){
System.out.println(username+","+age);
return "success";
}
@RequestBody:获取请求体,和上面参数类似,只不过这个是获得一个key-value,key-value的形式的参数。也是作用在方法形参上
@PathVaribale:用于绑定url中的占位符,也就是吧url中的那个id拿过来,赋值给形参
@RequestMapping("/usePathVariable/{id}")
public String usePathVariable(@PathVariable("id") Integer id){
System.out.println(id);
return "success";
}
@ResponseBody:作用于方法上,把返回的数据封装成json响应给前端。
三、MyBatis
mybatis是一个基于java的持久层的框架,在了解Mybatis之前可以先看下图:
mybatis框架的作用,或者说mybatis它帮我们解决了什么问题?
在了解这个之前,我们先要回顾一下之前我们是如何通过java来访问数据库的,答案就是使用jdbc, JDBC是sun公司推出的一套访问数据库的标准规范,然后每个不同的数据库厂商例如mysql或者oracle等他们去实现这条规范,从而使得我们可以达到通过java来访问数据库的目的。
下面来回顾一下通过传统的JDBC访问数据库的过程:
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1.加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//2.通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatischaracterEncoding=utf8","root", "root");
//3.定义 sql 语句 ?表示占位符
String sql = "select * from user where username = ?";
//4.获取预处理 statement
preparedStatement = connection.prepareStatement(sql);
//5.设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的
参数值
preparedStatement.setString(1, "王五");
//6.向数据库发出 sql 执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
//7.遍历查询结果集
while(resultSet.next()){
System.out.println(resultSet.getString("id")+"
"+resultSet.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//释放资源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
由上面的过程我们可以发现jdbc存在的很多问题:
- 首先就是太复杂,从加载驱动到获取连接,写sql,执行,对返回的结果集处理,这一系列的步骤导致我们开发复杂度大大增加。
- 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
- Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java代码。
- 对结果集解析存在硬编码(查询列名),sql 变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成 pojo 对象解析比较方便。(就是结果集解析麻烦)
其实我们想一下,在开发中好像只有sql是我们重点关注和变化的,而Mybatis的出现就是帮我们解决JDBC中这一系列复杂繁琐的操作,使我们只需要去关注sql本身即可。
Mybatis环境搭建:
- 创建maven工程,pom文件导入mybatis相应坐标
- 编写持久层接口IUserDao
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAll();
}
- 编写持久层接口的xml映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.IUserDao">
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
</mapper>
- 编写SqlMapConfig.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置 mybatis 的环境 -->
<environments default="mysql">
<!-- 配置 mysql 的环境 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的信息:用的是数据源(连接池) -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ee50"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<!-- 告知 mybatis 映射配置的位置 -->
<mappers>
<mapper resource="com/itheima/dao/IUserDao.xml"/>
</mappers>
</configuration>
- 编写测试类
public class MybatisTest {
public static void main(String[] args)throws Exception {
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建 SqlSessionFactory 的构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.使用构建者创建工厂对象 SqlSessionFactory
SqlSessionFactory factory = builder.build(in);
//4.使用 SqlSessionFactory 生产 SqlSession 对象
SqlSession session = factory.openSession();
//5.使用 SqlSession 创建 dao 接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//6.使用代理对象执行查询所有方法
List<User> users = userDao.findAll();
for(User user : users) {
System.out.println(user);
}
//7.释放资源
session.close();
in.close();
}
}
Mybatis是如何解决传统jdbc的问题的:
1.在 SqlMapConfig.xml 中配置数据链接池,使用连接池管理数据库链接。解决数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能。
2.将 Sql 语句配置在 XXXXmapper.xml 文件中与 java 代码分离。解决sql卸载代码里不易维护的问题
3.Mybatis 自动将 java 对象映射至 sql 语句,通过 statement 中的 parameterType 定义输入参数的类型。解决sql语句传参的麻烦性
4.对结果集解析麻烦,sql 变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成 pojo 对象解析比较方便。
解决:Mybatis 自动将 sql 执行结果映射至 java 对象,通过 statement 中的 resultType 定义输出结果的类型。
Mybatis中的延迟加载
就是在一对多,多对多查询时,不查另一个类。主要是通过 association、collection 实现一对一及一对多映射。association、collection 具备延迟加载功能
在sqlmapconfig.xml配置文件中开启延迟加载:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
Mybatis中的缓存:
一级缓存:一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。简单来说就是我们在一次查询中,先查了id为1的User信息,然后又查了一遍,其实底层就访问了一次数据库。(这里说的是一个SqlSession)
二级缓存:二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
Mybatis基于注解的开发:
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用