Spring框架
什么是Bean:可重用组件,在java中javabean:用java语言编写的可重用组件。
Spring容器就是通过配置文件来获取我们的bean对象的。(通过读取配置文件的内容,通过反射来创建对象)
Spring IOC就是把创建对象的主动权交给Spring 容器(即BeanFactory或者ApplicationContext),把创建对象和管理对象、注入对象的权力交给了容器。从而是程序解耦,降低程序间的依赖关系。
ApplicationContext (容器接口)的三个常用实现类:
ClassPathXmlApplicationContext
: 它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不存在的话,加载不了
FileSystemXmlApplicationContext
:它可以加载磁盘任意路径的配置文件(文件必须有访问权限)
AnnotationConfigApplicationContext
:它是用于读取注解来创建容器的。
核心容器的两个接口引发出的问题:
ApplicationContext
: 它在构建核心容器时,创建对象采取的策略是立即加载的方式。也就是说,只要一读取完配置文件就立即创建对象。(单例模式适用)
BeanFactory
:创建对象采取的策略是延迟加载的策略。就是当根据id获取对象时,才创建对象。(多例对象适用)
其实我们也可以采用配置的不同(singleton
、prototype
)来采取不同的创建对象方式
创建Bean的三种方式
第一种:使用默认构造函数方式创建,在spring的配置文件中使用bean标签,配以id和class属性后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,若类中没有构造函数,则无法创建bean对象。
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
....
....
第二种:使用普通工厂中的方法来创建对象。下面就是采用InstanceFactory中的getAccountService方法来创建accountService对象
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
....
....
第三种:使用工厂中的静态方法创建bean对象
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
bean对象的作用范围
bean标签的scope属性:
作用:用于指定bean的作用范围
取值:
`singleton` : 单例的
`prototype` :多例的
`request` : 作用于web应用的请求范围
`session` :作用于web应用的会话范围
`global-session` :作用于集群环境的会话范围(全局会话范围),当不是集群环境时,作用相当于session
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="singleton"></bean>
bean对象的生命周期
init-method
:在对象创建时执行的方法
destory-method
:在对象销毁时执行的方法
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="singleton" init-method="init" destory-method="destory"></bean>
单例对象
出生:当容器创建时,对象出生
活着:只要容器在,对象就在
死亡:容器销毁,对象也销毁了
总结:生命周期和容器相同
多例对象
出生:当使用对象时,对象出生
活着:使用过程中一直活着
死亡:当对象长时间不用时,且没有别的对象引用时,由Java垃圾回收器回收
spring的依赖注入
依赖注入:Dependency Injection
IOC的作用:降低程序间的耦合(依赖关系)
依赖关系的管理:以后交给Spring来维护、在当前类需要其他类的对象时,由Spring为我们提供,我们只需要在配置文件中说明即可
依赖注入能注入的数据:
基本类型和String
其他bean类型(在配置文件中或者注解中配置过bean)
复杂类型/集合类型
依赖注入注入的方式:使用构造函数注入,使用set方法注入,使用注解注入。
**1.使用构造函数提供**
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" >
<constructor-arg name="name" value="jjp"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!--配置一个日期对象-->
<bean id="now" class="java.util.Date"></bean>
**2.使用set方法提供**
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" >
<property name="name" value="jjp"></property>
<property name="age" value="age"></property>
<property name="birthday" ref="now"></property>
</bean>
<!--配置一个日期对象-->
<bean id="now" class="java.util.Date"></bean>
复杂类型的注入/集合类型的注入
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" >
<property name="myStrs">
<!--数组-->
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myList">
<!--列表-->
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="mySet">
<!--集合-->
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myMap">
<!--Map-->
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB" value="bbb"></entry>
<entry key="testC" value="ccc"></entry>
</map>
</property>
<property name="myProps">
<!--属性文件-->
<props>
<prop key="testA">aaa</prop>
<prop key="testB">bbb</prop>
</props>
</property>
</bean>
<!--配置一个日期对象-->
<bean id="now" class="java.util.Date"></bean>
spring ioc中的常用注解
用于创建对象的:
它们的作用和Spring中bean标签的作用是一致的
@Component
:
把当前对象存入spring容器中
属性值:value:用于指定bean的id、当我们不写时,他的默认值是当前类名,且首字母小写
@Controller
:用于表现层的、与Component注解功能一致
@Service
:用于业务层、与Component注解功能一致
@Repository
:用于持久层、与Component注解功能一致
用于注入对象的
它们的作用和在bean标签内部使用property标签注入对象和基本类型数据或者集合时的功能是一样的。
@Autowired
:
自动按照类型注入、只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
出现位置:可以是变量上,也可以是方法上。
@Qulifier
:
在按照类型中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用(要和Autowired联合使用)。但在给方法参数注入时可以单独使用
属性:value:用于指定注入bean的id
@Resource
:
直接按照bean的id注入、可以直接使用
属性: name:用于指定bean的id。
以上三个注解注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现、另外集合类型的注入只能通过xml实现
@Value
:
用于注入基本类型和String类型的数据
属性:
value:用于指定数据的值、它可以使用Spring中的El表达式SpEL 写法:`${表达式}`
用于改变作用范围的
它们的作用和在bean标签中使用scope属性实现的功能是一样的
@Scope
:
作用:用于指定bean的作用范围
属性:value:常用取值 : singleton prototype
和生命周期相关的
它们的作用和bean标签中使用init-method和destory-method的作用是一样的。
@PostConstructor
:用于指定初始化的方法
@PreDestory
:用于指定销毁之前执行的方法
Spring IOC 新注解
@Configuration
: 指定当前类为一个配置类
@ComponentScan
: 用于通过注解指定Spring在创建容器时要扫描的包
属性:value:指定要扫描的包,例如:com.itheima
basepackages:和value一样的功能
使用ComponentScan等同于
<context:component-scan base-package="com.itheima"></context:component-scan>
@Bean
: 把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:name:用于指定bean的id。当不写时,默认值是当前方法的名称
@Import
:用于导入其他配置类
属性:value:指定其他配置类的字节码、当我们使用Import注解之后,有Import注解的类就是主配置类。
@PropertySource
: 用于指定properties文件的位置
属性:value:指定文件名称。
@RunWith
@ContextConfiguration
spring AOP
动态代理:
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法进行加强
分类:
基于接口的动态代理
基于子类的动态代理
基于接口的动态代理
涉及的类:Proxy
提供者:JDK官方
如何创建代理对象:
使用Proxy类中的`newProxyInstance方法`
创建代理对象的要求:
被代理类至少实现一个接口、如果没有则不能使用
`newProxyInstance方法的参数`:
`ClassLoader`: 类加载器:它是用于加载代理对象字节码的类加载器
`Class[]`:字节码数组:用于让代理对象和被代理对象有相同方法
`InvocationHandler`: 用于提供增强的方法、如何代理。
基于子类的动态代理:
《待续》
什么是AOP
面向切面编程:把程序中的重复代码抽取出来、在需要执行的时候、使用动态代理技术,在不修改源码的基础上,对我们已有方法进行增强。
AOP的作用和优势:
在程序运行期间、不修改源代码对已有方法进行增强。
AOP相关术语
Jointpoint
:连接点:所谓连接点是指哪些被拦截到得的点。在spring中、这些点值得是方法、是因为spring只支持方法类型的连接点
Pointcut
:切入点:所谓切入点是指我们要对哪些Jointpoint进行拦截的定义。
Advice
:通知:拦截后要做的事情,分为:前置通知、后置通知、异常通知、最终通知、环绕通知。
AOP的Spring配置(XML)
<!--配置spring中的IOC 把service对象配置进来-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!--Spring中基于XML的AOP配置步骤
1.把通知bean交给Spring来管理
2.使用aop:config标签表明开始AOP的配置
3.使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指通知类bean的id
4.在aop:aspect标签内部使用对应的标签来配置通知的类型
比如前置通知
aop:before:表示配置前置通知
method属性:用于指定通知bean类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
标准的表达式写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
全通配写法:
* *..*.*(..)
-->
<!--配置Logger类-->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--配置Aop-->
<aop:config>
<!--
配置切入点表达式,id属性用于指定表达式的唯一标识符。expression属性用于指定表达式的内容
此标签写在aop:aspect标签内部只能当前切面使用
它还可以写在aop:aspect外面,此时就变成了所有切面可用
-->
<aop:pointcut id="pt1" expression=”execution(* com.itheima.service.impl.*.*(..))“></aop:pointcut>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,并且建立通知方法与切入点方法的关联-->
<aop:before method="printLog" pointcut-ref="pt1"></aop:before>
<!---后置通知 <aop:after-returning>-->
<aop:after-returning method="printLog" pointcut-ref="pt1"></aop:after-returning>
<!---异常通知 <aop:after-throwing>-->
<aop:after-throwing method="printLog" pointcut-ref="pt1"></aop:after-throwing>
<!---最终通知 <aop:after>-->
<aop:after method="printLog" pointcut-ref="pt1"></aop:after>
<!--环绕通知-->
<aop:around method="printLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
AOP的Spring注解
@Aspect
:表示当前通知是一个切面类
@Before
:前置通知
@AfterReturning
:后置通知
@AfterThrowing
:异常通知
@After
:最终通知
@PointCut
:切入点表达式
@Around
:环绕通知
Spring 中JdbcTemplate的使用
JdbcTemplate的作用:和数据库进行交互,实现对表的curd操作
Spring声明式事务控制
Spring中事务控制的API介绍
PlatformTransactionManager
: 此接口是spring的事务管理器,它里面提供了我们常用的事务操作方法。
实现类有:
`DataSourceTranscationManager`
`HibernateTransactionManager`
TransactionDefinition
:它是事务的定义信息对象
BeanFactory和ApplicationContext有什么区别
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
依赖关系
BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
- 继承MessageSource,因此支持国际化。
- 统一的资源文件访问方式。
- 提供在监听器中注册bean的事件。
- 同时加载多个配置文件。
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
加载方式
BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
Spring 如何设计容器的,BeanFactory和ApplicationContext的关系详解
BeanFactory 简单粗暴,可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”。
ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的 bean。
为了更直观的展示 “低级容器” 和 “高级容器” 的关系,这里通过常用的 ClassPathXmlApplicationContext 类来展示整个容器的层级 UML 关系。
有点复杂? 先不要慌,我来解释一下。
最上面的是 BeanFactory,下面的 3 个绿色的,都是功能扩展接口,这里就不展开讲。
看下面的隶属 ApplicationContext 粉红色的 “高级容器”,依赖着 “低级容器”,这里说的是依赖,不是继承哦。他依赖着 “低级容器” 的 getBean 功能。而高级容器有更多的功能:支持不同的信息源头,可以访问文件资源,支持应用事件(Observer 模式)。
通常用户看到的就是 “高级容器”。 但 BeanFactory 也非常够用啦!
左边灰色区域的是 “低级容器”, 只负载加载 Bean,获取 Bean。容器其他的高级功能是没有的。例如上图画的 refresh 刷新 Bean 工厂所有配置,生命周期事件回调等。
小结
说了这么多,不知道你有没有理解Spring IoC? 这里小结一下:IoC 在 Spring 里,只需要低级容器就可以实现,2 个步骤:
加载配置文件,解析成 BeanDefinition 放在 Map 里。
调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。
上面就是 Spring 低级容器(BeanFactory)的 IoC。
至于高级容器 ApplicationContext,他包含了低级容器的功能,当他执行 refresh 模板方法的时候,将刷新整个容器的 Bean。同时其作为高级容器,包含了太多的功能。一句话,他不仅仅是 IoC。他支持不同信息源头,支持 BeanFactory 工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等等。
常见的ApplicationContext实现类
FileSystemXmlApplicationContext :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。
ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。
WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。
什么是Spring IOC
控制反转IoC是一个很大的概念,可以用不同的方式来实现。其主要实现方式有两种:依赖注入和依赖查找
依赖注入:相对于IoC而言,依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入(Dependency Injection),即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。
依赖注入的基本原则
依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter)或者是构造器传递给需要的对象。
依赖注入有什么优势
依赖注入之所以更流行是因为它是一种更可取的方式:让容器全权负责依赖查询,受管组件只需要暴露JavaBean的setter方法或者带参数的构造器或者接口,使容器可以在初始化时组装对象的依赖关系。其与依赖查找方式相比,主要优势为:
查找定位操作与应用代码完全无关。
不依赖于容器的API,可以很容易地在任何容器以外使用应用对象。
不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器。
有哪些不同类型的依赖注入的实现方式
依赖注入是时下最流行的IoC实现方式,依赖注入分为接口注入(Interface Injection),Setter方法注入(Setter Injection)和构造器注入(Constructor Injection)三种方式。其中接口注入由于在灵活性和易用性比较差,现在从Spring4开始已被废弃。
构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。
构造器依赖注入和 Setter方法注入的区别
构造函数注入 | setter方法注入 |
---|---|
没有部分注入 | 有部分注入 |
不会覆盖setter属性 | 会覆盖setter属性 |
任意修改都会创建一个新实例 | 任意修改不会创建一个新实例 |
适用于设置很多属性 | 适用于设置少量属性 |
两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。
Spring Security
什么是Spring Security
Spring Security是基于Spring AOP和Servlet过滤器的安全框架。它提供全面的安全性解决方案,同时再web请求级和方法调用级处理身份确认和授权。
Security的核心功能
- 1.认证(你是谁,用户/设备/系统)
- 2.验证(你能干什么、权限管理)
- 3.攻击防护(防止伪造身份)
Security和shiro的区别
优势
- 1.Spring Security是基于Spring开发、配合Spring会更加方便
- 2.功能比shiro更加丰富,例如安全防护方面
- 3.社区资源相对丰富
- 4.如果使用的是springboot,springcloud的话,三者可以无缝集成
劣势
- shiro的配置和使用相对简单,security上手较为复杂
- shiro依赖性低,不需要任何框架和容器,可以独立运行,而security依赖spring容器
基本的整合过程
添加依赖包
spring-boot-starter-web
spring-boot-starter-security
就可以使用spring security了
取消SpringBoot Security的自动配置
@SpringBootApplication(exclude=SecurityAutoConfiguration)
自定义用户名和密码
在application.properties里面
spring.security.user.name=admin
spring.security.user.password=123456
基于内存的认证信息
我们如果要在内存中初始化我们的认证信息的话,那么需要重写 WebSecurityConfigurerAdapter
类中的configure
方法
configure(AuthenticationManagerBuilder auth)
然后通过auth
对象的inMemoryAuthentication()
方法指定认证信息:
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).WithUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles();
也可以通过@Bean方式来指定密码加密方式
@Bean //注入PasswordEncoder
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
基于内存的角色授权
通过AuthenticationManagerBuilder
的roles
方法,就可以指定角色
auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("123456")).roles("beijingAdmin","shanghaiAdmin")
如何开启方法级别的安全控制
在已经添加了@Configuration
注解的类上再添加@EnableGlobalMethodSecurity
注解即可
举个例子
@EnableGlobalMethodSecurity(prePostEnabled=true) //会拦截注解了@PreAuthrize注解的配置
基于数据库的身份认证和角色授权
Spring 循环依赖
-
什么是循环依赖
A类对象的创建需要B对象
B类对象的创建需要A对象
那么就会产生循环依赖
-
怎么解决循环依赖
构造器的循环依赖spring解决不了
setter方法的循环依赖,spring采用三级缓存来解决。
-
spring为什么用三级缓存来解决循环依赖
spring解决
循环依赖
的做法是未等bean创建
完就先将实例曝光出去,方便其他bean的引用。同时还提到了三级缓存,最先曝光到第三级缓存singletonFactories
中。总结一下循环依赖,spring只能解决setter注入单例模式下的循环依赖问题。要想解决循环依赖必须要满足2个条件:
- 需要用于
提前曝光
的缓存 - 属性的
注入时机
必须发生在提前曝光
动作之后,不管是填充
还是初始化
都行,总之不能在实例化
,因为提前曝光动作在实例化之后
理解了这2点就可以轻松驾驭循环依赖了。比如构造器注入是不满足第二个条件,曝光时间不对。而原型模式则是缺少了第一个条件,没有提前曝光的缓存供使用
- 需要用于
Spring框架中事件的种类
Spring 提供了以下5种标准的事件:
(1)上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
(2)上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
(3)上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
(4)上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
(5)请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。
如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。
Spring事务方式和种类
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
(1)Spring事务的种类:
spring支持编程式事务管理和声明式事务管理两种方式:
①编程式事务管理使用TransactionTemplate。
②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。
声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式,使业务代码不受污染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
spring事务的7种传播行为
-
PROPAGATION_REQUIRED (Spring 默认传播级别)
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
-
PROPAGATION_SUPPORTS
如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。
-
PROPAGATION_MANDATORY
如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
-
PROPAGATION_REQUIRES_NEW
使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作为事务管理器。 它会开启一个新的事务。如果一个事务已经存在,则先将这个存在的事务挂起。
-
PROPAGATION_NOT_SUPPORTED
PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。
-
PROPAGATION_NEVER
总是非事务地执行,如果存在一个活动事务,则抛出异常。
-
PROPAGATION_NESTED
PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:
它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。 使用 PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。
使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。
spring事务隔离级别
spring默认的事务隔离级别就是数据库的事务隔离级别。
1.首先说明一下事务并发引起的三种情况:
1) Dirty Reads 脏读
一个事务正在对数据进行更新操作,但是更新还未提交,另一个事务这时也来操作这组数据,并且读取了前一个事务还未提交的数据,而前一个事务如果操作失败进行了回滚,后一个事务读取的就是错误数据,这样就造成了脏读。
2) Non-Repeatable Reads 不可重复读
一个事务多次读取同一数据,在该事务还未结束时,另一个事务也对该数据进行了操作,而且在第一个事务两次次读取之间,第二个事务对数据进行了更新,那么第一个事务前后两次读取到的数据是不同的,这样就造成了不可重复读。
3) Phantom Reads 幻像读
第一个数据正在查询符合某一条件的数据,这时,另一个事务又插入了一条符合条件的数据,第一个事务在第二次查询符合同一条件的数据时,发现多了一条前一次查询时没有的数据,仿佛幻觉一样,这就是幻像读。
非重复度和幻像读的区别:
非重复读是指同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。
幻像读是指同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻像读。
表面上看,区别就在于非重复读能看见其他事务提交的修改和删除,而幻像能看见其他事务提交的插入。
Spring创建bean的流程
首先说一下Servlet的生命周期:实例化,初始化init,接收请求service,销毁destory;
Spring上下文中的Bean生命周期也类似,如下:
(1):实例化Bean:
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有Bean。
(2):设置对象属性(依赖注入)
实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入。
(3)处理Aware接口:
接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;
②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
③如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
(4)BeanPostProcessor:
如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。
(5)InitializingBean 与 init-method:
如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
(6)如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;
以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
(7)DisposableBean:
当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;
(8)destroy-method:
最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
Spring如何处理线程并发问题
在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
Spring框架中用到了哪些设计模式
(1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例
(2)单例模式:Bean默认为单例模式
(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
(4)模板方法:用来解决代码重复问题,比如。RestTemplate,JmsTemplate,JpaTemplat
(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制定更新,如Spring中Listener的实现:ApplicationListener。
SpringBoot框架
自动装配原理
Spring Boot
启动的时候会通过@EnableAutoConfiguration
注解找到META-INF/spring.factories
配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以AutoConfiguration
结尾来命名的,它实际上就是一个JavaConfig
形式的Spring
容器配置类,它能通过以Properties
结尾命名的类中取得在全局配置文件中配置的属性如:server.port
,而XxxxProperties
类是通过@ConfigurationProperties
注解与全局配置文件中对应的属性进行绑定的。
SpringBootApplication组合注解
SpringBootConfiguration:
读取springboot的配置文件application.properties,对于没有设定的内容默认配置。
EnableAutoConfiguration:
根据依赖的jar包,将SpringBoot工程需要的其他内容进行自动配置。
ComponentScan:
启动类所在的包路径的同级包和下级包中所有Spring需要加载,启动的注解一旦存在,将会自动在Spring容器启动时,加载到内存中,等待注入和使用。
SpringMVC
SpringMVC八大注解
@Controller:
用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller 对象。分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping 注解。@Controller 只是定义了一个控制器类,而使用@RequestMapping 注解的方法才是真正处理请求的处理器。
@RequestMapping:
RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
RequestMapping注解有六个属性,下面我们把她分成三类进行说明(下面有相应示例)。
1、 value, method;
value: 指定请求的实际地址,指定的地址可以是URI Template 模式(后面将会说明);
method: 指定请求的method类型, GET、POST、PUT、DELETE等;
2、consumes,produces
consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;
3、params,headers
params: 指定request中必须包含某些参数值是,才让该方法处理。
headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求。
@Resource 和 @AutoWired
@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
区别:
@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。
@Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
@PathVariable:
用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数。
@requestParam:
@requestParam主要用于在SpringMVC后台控制层获取参数,类似一种是request.getParameter(“name”),它有三个常用参数:defaultValue = “0”, required = false, value = “isApp”;defaultValue 表示设置默认值,required 铜过boolean设置是否是必须要传入的参数,value 值表示接受的传入的参数类型。
@ResponseBody:
作用: 该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。
使用时机:返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用;
MyBatis框架
MyBatis简介
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,是一个基于Java的持久层框架。
- 持久层: 可以将业务数据存储到磁盘,具备长期存储能力,只要磁盘不损坏,在断电或者其他情况下,重新开启系统仍然可以读取到这些数据。
- 优点: 可以使用巨大的磁盘空间存储相当量的数据,并且很廉价****灵活,几乎可以代替 JDBC,同时提供了接口编程。
- 缺点:慢(相对于内存而言)
为什么使用MyBatis
在我们传统的 JDBC 中,我们除了需要自己提供 SQL 外,还必须操作 Connection、Statment、ResultSet,不仅如此,为了访问不同的表,不同字段的数据,我们需要些很多雷同模板化的代码,闲的繁琐又枯燥。
而我们在使用了 MyBatis 之后,只需要提供 SQL 语句就好了,其余的诸如:建立连接、操作 Statment、ResultSet,处理 JDBC 相关异常等等都可以交给 MyBatis 去处理,我们的关注点于是可以就此集中在 SQL 语句上,关注在增删改查这些操作层面上。
并且 MyBatis 支持使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
Mybatis中的${ }和#{ }有什么区别
#{}是预编译处理,${}是字符串替换。
Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
Mybatis在处理 时 , 就 是 把 {}时,就是把 时,就是把{}替换成变量的值。
使用#{}可以有效的防止SQL注入,提高系统安全性。
更简单一些:
- #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by “111”, 如果传入的值是id,则解析成的sql为order by “id”.
- $将传入的数据直接显示生成在sql中。如:order by u s e r i d user_id userid,如果传入的值是111,那么解析成sql时的值为order by user_id, 如果传入的值是id,则解析成的sql为order by id.
-
使用#{}格式的语法在mybatis中使用Preparement语句来安全的设置值,执行sql类似下面的:
PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1,id);
-
不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER BY,你可以这样来使用:
ORDER BY ${columnName}
此时MyBatis 不会修改或转义字符串。
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);
这种方式的缺点是: 以这种方式接受从用户输出的内容并提供给语句中不变的字符串是不安全的,会导致潜在的 SQL 注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。
PrepareStatement和Statement的区别
- Statement不对sql语句作处理,直接交给数据库;而PreparedStatement支持预编译,会将编译好的sql语句放在数据库端,相当于缓存。对于多次重复执行的sql语句,使用PreparedStatement可以使得代码的执行效率更高。(语句在被DB的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中(相当于一个涵数)就会得到执行.这并不是说只有一个Connection中多次执行的预编译语句被缓存,而是对于整个DB中,只要预编译的语句语法和缓存中匹配.那么在任何时候就可以不需要再次编译而可以直接执行.而statement的语句中,即使是相同一操作,而由于每次操作的数据不同所以使整个语句相匹配的机会极小,几乎不太可能匹配.)
- Statement的sql语句使用字符串拼接的方式,容易导致出错,且存在sql注入的风险;PreparedStatement使用“?”占位符提升代码的可读性和可维护性,并且这种绑定参数的方式,可以有效的防止sql注入。(prepareStatement对象防止sql注入的方式是把用户非法输入的单引号用\反斜杠做了转义,从而达到了防止sql注入的目的)
MyBatis是什么?
MyBatis 是一款优秀的持久层框架,一个半 ORM(对象关系映射)框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
ORM是什么
ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。
为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
传统JDBC开发存在的问题
- 频繁创建数据库连接对象、释放,容易造成系统资源浪费,影响系统性能。可以使用连接池解决这个问题。但是使用jdbc需要自己实现连接池。
- sql语句定义、参数设置、结果集处理存在硬编码。实际项目中sql语句变化的可能性较大,一旦发生变化,需要修改java代码,系统需要重新编译,重新发布。不好维护。
- 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
- 结果集处理存在重复代码,处理麻烦。如果可以映射成Java对象会比较方便。
JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?
1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
解决:在mybatis-config.xml中配置数据链接池,使用连接池管理数据库连接。
2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决: Mybatis自动将java对象映射至sql语句。
4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象。
Mybatis优缺点
优点
与传统的数据库访问技术相比,ORM有以下优点:
- 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用
- 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接
- 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)
- 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护
- 能够与Spring很好的集成
缺点
-
SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
-
SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
Hibernate 和 MyBatis 的区别
相同点
都是对jdbc的封装,都是持久层的框架,都用于dao层的开发。
不同点
映射关系
- MyBatis 是一个半自动映射的框架,配置Java对象与sql语句执行结果的对应关系,多表关联关系配置简单
- Hibernate 是一个全表映射的框架,配置Java对象与数据库表的对应关系,多表关联关系配置复杂
SQL优化和移植性
- Hibernate 对SQL语句封装,提供了日志、缓存、级联(级联比 MyBatis 强大)等特性,此外还提供 HQL(Hibernate Query Language)操作数据库,数据库无关性支持好,但会多消耗性能。如果项目需要支持多种数据库,代码开发量少,但SQL语句优化困难。
- MyBatis 需要手动编写 SQL,支持动态 SQL、处理列表、动态生成表名、支持存储过程。开发工作量相对大些。直接使用SQL语句操作数据库,不支持数据库无关性,但sql语句优化容易。
开发难易程度和学习成本
- Hibernate 是重量级框架,学习使用门槛高,适合于需求相对稳定,中小型的项目,比如:办公自动化系统
- MyBatis 是轻量级框架,学习使用门槛低,适合于需求变化频繁,大型的项目,比如:互联网电子商务系统
总结
MyBatis 是一个小巧、方便、高效、简单、直接、半自动化的持久层框架,
Hibernate 是一个强大、方便、高效、复杂、间接、全自动化的持久层框架。
MyBatis的解析和运行原理
1、 创建SqlSessionFactory
2、 通过SqlSessionFactory创建SqlSession
3、 通过sqlsession执行数据库操作
4、 调用session.commit()提交事务
5、 调用session.close()关闭会话
MyBatis的工作原理
1)读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
2)加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。
为什么需要预编译
- 定义:
SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。 - 为什么需要预编译
JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。预编译阶段可以优化 SQL 的执行。预编译之后的 SQL 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的SQL,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。同时预编译语句对象可以重复利用。把一个 SQL 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个SQL,可以直接使用这个缓存的 PreparedState 对象。Mybatis默认情况下,将对所有的 SQL 进行预编译。
Mybatis都有哪些Executor执行器?
Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
Mybatis延迟加载?
延迟加载也叫作懒加载,比方说有数据库中存储着两张表(studentclass和student),并且这两张表形成了一对多的关系,如果不采用延迟加载(立即加载),查询时会将这两张表同时查询出来;如果想要 暂时只查询一的一方,而多的一方先不查询,而是在需要时再查询,那么这种就是延迟加载。
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。
#{}和${}的区别
- #{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理。
- Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
- Mybatis在处理时,是原值传入,就是把{}时,是原值传入,就是把时,是原值传入,就是把{}替换成变量的值,相当于JDBC中的Statement编译
- 变量替换后,#{} 对应的变量自动加上单引号 ‘’;变量替换后,${} 对应的变量不会加上单引号 ‘’
- #{} 可以有效的防止SQL注入,提高系统安全性;${} 不能防止SQL 注入
- #{} 的变量替换是在DBMS 中;${} 的变量替换是在 DBMS 外
模糊查询like语句该怎么写
(1)’%${question}%’ 可能引起SQL注入,不推荐
(2)"%"#{question}"%" 注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ ',所以这里 % 需要使用双引号" ",不能使用单引号 ’ ',不然会查不到任何结果。
(3)CONCAT(’%’,#{question},’%’) 使用CONCAT()函数,推荐
(4)使用bind标签
<select id="listUserLikeUsername" resultType="com.jourwon.pojo.User">
<bind name="pattern" value="'%' + username + '%'" />
select id,sex,age,username,password from person where username LIKE #{pattern}
</select>
在mapper中如何传递多个参数
方法1:顺序传参法
public User selectUser(String name, int deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{0} and dept_id = #{1}
</select>
#{}里面的数字代表传入参数的顺序。这种方法不建议使用,sql层表达不直观,且一旦顺序调整容易出错。
方法2:@Param注解传参法
public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
#{}里面的名称对应的是注解@Param括号里面修饰的名称。这种方法在参数不多的情况还是比较直观的,推荐使用。
方法3:Map传参法
public User selectUser(Map<String, Object> params);
<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
#{}里面的名称对应的是Map里面的key名称。这种方法适合传递多个参数,且参数易变能灵活传递的情况。
方法4:Java Bean传参法
public User selectUser(User user);
<select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
#{}里面的名称对应的是User类里面的成员属性。这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑处理方便,推荐使用。