目录
Spring篇
spring是什么?
spring 有哪些主要模块?
Spring的IoC理解
Spring的AOP理解
AOP详解:
解释一下什么是 AOP?
AOP 的原理(重要)⭐
JDK 动态代理和 CGLIB 的区别⭐
实现原理
AspectJ 和 Spring AOP
区别
BeanFactory和ApplicationContext有什么区别?
Spring Bean的生命周期?
解释Spring支持的几种bean的作用域。
Spring框架中的单例Beans是线程安全的么?
Spring如何处理线程并发问题?
创建Bean的三种方式
Spring中的DI(依赖注入)
循环依赖
循环依赖是什么?⭐
Spring 如何解决循环依赖?⭐
Spring常用的注解
Spring 中 注解@Autowired和@Resources的区别
Spring中用到哪些设计模式?
Spring事务的实现方式和实现原理:
解释一下Spring AOP里面的几个名词:
Spring通知有哪些类型?
SpringMVC篇
springmvc执行流程
spring mvc 有哪些组件?
Spring为什么要结合使用HandlerMapping以及HandlerAdapter来处理Handler?
SpringMVC常用注解
如何解决POST请求中文乱码问题,GET的又如何处理呢?
SpringMVC怎么样设定重定向和转发的?
SpringMvc的控制器是不是单例模式,如果是,有什么问题,怎么解决?
SpingMvc中的控制器的注解一般用那个,有没有别的注解可以替代?
如果在拦截请求中,我想拦截get方式提交的方法,怎么配置?
SpringMVC注解原理
SpringMvc里面拦截器是怎么写的
Mybatis篇
1、什么是Mybatis?
5、MyBatis与Hibernate有哪些不同?
6、#{}和${}的区别是什么?
7、当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
8、 模糊查询like语句该怎么写?
9、通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
10、Mybatis是如何进行分页的?分页插件的原理是什么?
11、Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
12、如何执行批量插入?
13、如何获取自动生成的(主)键值?
14、在mapper中如何传递多个参数?
15、Mybatis动态sql有什么用?执行原理?有哪些动态sql?
16、Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
17、Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
18、为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
19、 一对一、一对多的关联查询 ?
20、MyBatis实现一对一有几种方式?具体怎么操作的?
21、MyBatis实现一对多有几种方式,怎么操作的?
22、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
23、Mybatis的一级、二级缓存:
24、什么是MyBatis的接口绑定?有哪些实现方式?
25、使用MyBatis的mapper接口调用时有哪些要求?
26、Mapper编写有哪几种方式?
27、简述Mybatis的插件运行原理,以及如何编写一个插件。
MyBatis编程步骤是什么样的?
Mybatis的常用标签或者注解用过哪些
从架构设计的角度,可以将 MyBatis 分为哪几层?每层都有哪些主要模块?
SqlMapConfig.xml中配置有哪些内容?
MyBatists的 resultType和resultmap的区别
JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?
使用MyBatis的mapper接口调用时有哪些要求?
mybatis是如何进行分页的?分页插件的原理是什么?
MyBatis 逻辑分页和物理分页的区别是什么?
Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
通常一个xml映射文件,都会写一个Dao接口与之对应,请问这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法可以重载吗?为什么?
Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
谈谈对mybatis中的sqlSession、sqlSessionFactoryBuild和sqlSessionFactory的理解。
Mybatis的#{}号和${}符
Spring篇
spring是什么?
Spring 是分层的 Java SE/EE 应用full-stack轻量级开源框架,以IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
spring 有哪些主要模块?
优势:
方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造
成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可
以更专注于上层的应用。
AOP编程的支持
通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP解决
声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,
提高开发效率和质量。
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可
做的事情。
方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz
等)的直接支持。
降低JavaEE API的使用难度
Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的
使用难度大为降低。
Java源码是经典学习范例
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以
及对Java技术的高深造诣。它的源代码无意是Java技术的最佳实践的范例。
IoC
IoC(Inverse of Control:控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。IoC 在其他语言中也有应用,并非 Spring 特有。IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。
AOP
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。
IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
Spring的IoC理解
(1)IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。
(2)最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
(3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。
(4)实现原理:
它是通过反射机制+工厂模式实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。
(5)依赖注入发生的时间
(1).用户第一次通过getBean方法向IoC容索要Bean时,IoC容器触发依赖注入。
(2).当用户在Bean定义资源中为元素配置了lazy-init属性,即让容器在解析注册Bean定义时进行预实例化,触发依赖注入。
Spring的AOP理解
OOP面向对象,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。
AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
①JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
②如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
(3)静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
InvocationHandler 的 invoke(Objectproxy,Methodmethod,Object[] args):proxy是最终生成的代理实例;method 是被代理目标实例的某个具体方法;args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
AOP的实现
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
动态代理:提供一个基础的接口,作为被调用类型(com.mycorp.HelloImpl)和代理类之间的统一入口,如 com.mycorp.Hello。
实现InvocationHandler,对代理对象方法的调用,会被分派到其 invoke 方法来真正实现动作。
通过 Proxy 类,调用其 newProxyInstance 方法,生成一个实现了相应基础接口的代理类实例,
Java在JDK1.3后引入的动态代理机制,使我们可以在运行期动态的创建代理类。使用动态代理实现AOP需要有四个角色:被代理的类,被代理类的接口,织入器,和InvocationHandler,而织入器使用接口反射机制生成一个代理类,然后在这个代理类中织入代码。被代理的类是AOP里所说的目标,InvocationHandler是切面,它包含了Advice和Pointcut。
如何使用Spring AOP
可以通过配置文件或者编程的方式来使用Spring AOP。 配置可以通过xml文件来进行,大概有四种方式:
1)配置ProxyFactoryBean,显式地设置advisors, advice, target等
2)配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象
3)通过aop:config来配置
4)通aop:aspectj-autoproxy过来配置,使用AspectJ的注解来标识通知及切入点
Spring AOP的实现
如何生成代理类:
Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理
切面是如何织入的?
InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。
AOP详解:
AOP(Aspect-OrientedProgramming,面向方面编程)它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。便于减少系统的重复代码,降低模块间的耦合度,核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。
性能监控,在方法调用前后记录调用时间,方法执行太长或超时报警。
缓存代理,缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。
软件破解,使用AOP修改软件的验证类的判断逻辑。
记录日志,在方法执行前后记录系统日志。
工作流系统,工作流系统需要将业务代码和流程引擎代码混合在一起执行,那么我们可以使用AOP将其分离,并动态挂接业务。
权限验证,方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,由业务代码捕捉。
AOP能干什么,也是AOP带来的好处
1:降低模块的耦合度
2:使系统容易扩展
3:设计决定的迟绑定:使用AOP,设计师可以推迟为将来的需求作决定,因为它
可以把这种需求作为独立的方面很容易的实现。
4:更好的代码复用性
解释一下什么是 AOP?
AOP(Aspect-Oriented Programming,面向切面编程),可以说是 OOP 的补充和完善。OOP 定义了从上到下的关系,但并不适合定义从左到右的关系,例如权限认证、日志、事务处理。这些导致了大量代码的重复,而不利于各个模块的重用。而AOP技术将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
AOP 的原理(重要)⭐
开启了 AOP 功能后,容器在注册 Bean 的后置处理器的时候,就会注册一个相关的后置处理器(AspectJAutoProxyCreator),在创建单实例 Bean 的时候,这个后置处理器就会拦截业务逻辑组件和切面组件的创建过程,怎么拦截呢?就是等组件创建完后,判断是否是通知方法,如果是就把切面的通知方法包装成增强器(Advisor),给业务逻辑组件(目标类)创建一个代理对象,代理的方式由 Spring 来判断返回这个代理对象,容器创建完成后这个代理对象执行目标方法的时候,执行拦截器链依次执行通知方法。
JDK 动态代理和 CGLIB 的区别⭐
静态代理与动态代理静态代理,是编译时增强,AOP 框架会在编译阶段生成 AOP 代理类,在程序运行前代理类的.class文件就已经存在了。常见的实现:JDK静态代理,AspectJ 。 动态代理,是运行时增强,它不修改代理类的字节码,而是在程序运行时,运用反射机制,在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
JDK 动态代理基于接口,所以只有接口中的方法会被增强,而 CGLIB 基于类继承,需要注意就是如果方法使用了 final 修饰,或者是 private 方法,是不能被增强的。
实现原理
JDK动态代理:基于反射,利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。 CGLIB动态代理:基于操作字节码,通过加载代理对象的类字节码,为代理对象创建一个子类,并在子类中拦截父类方法并织入方法增强逻辑。底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的。
AspectJ 和 Spring AOP
区别
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。
BeanFactory和ApplicationContext有什么区别?
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
(1)BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
a. 国际化支持, b. 资源访问, 如URL和文件 c. 事件传递
①继承MessageSource,因此支持国际化。
②统一的资源文件访问方式。
③提供在监听器中注册bean的事件。
④同时加载多个配置文件。
⑤载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
(2)①BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
③相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
(3)BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
(4)BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
public class Client {/*** 获取spring的Ioc核心容器,并根据id获取对象** ApplicationContext的三个常用实现类:*ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)*FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)**AnnotationConfigApplicationContext:它是用于读取注解创建容器的,是明天的内容。** 核心容器的两个接口引发出的问题:* ApplicationContext:单例对象适用 采用此接口*它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。** BeanFactory: 多例对象使用*它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。* @param args*/public static void main(String[] args) {//1.获取核心容器对象ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");// ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\zhy\\Desktop\\bean.xml");//2.根据id获取Bean对象IAccountService as = (IAccountService)ac.getBean("accountService");IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);System.out.println(as);System.out.println(adao);as.saveAccount();/**--------BeanFactory----------**/Resource resource = new ClassPathResource("bean.xml");BeanFactory factory = new XmlBeanFactory(resource);IAccountService as = (IAccountService)factory.getBean("accountService");System.out.println(as);}}
Spring Bean的生命周期?
首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;
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属性,会自动调用其配置的销毁方法。
Bean 的生命周期概括起来就是4 个阶段:
实例化(Instantiation);
属性赋值(Populate);
初始化(Initialization);
销毁(Destruction)。
详情——Spring Bean 源码解析
解释Spring支持的几种bean的作用域。
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="singleton"></bean>
bean标签的scope属性:
作用:用于指定bean的作用范围
取值: 常用的就是单例的和多例的
singleton:单例的(默认值)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着。
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收(GC)
Spring容器中的bean可以分为5个范围:
(1)singleton:默认,单例,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。
(2)prototype:多例,为每一个bean请求提供一个实例。
(3)request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
(4)session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
(5)global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。
Spring框架中的单例Beans是线程安全的么?
Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。
1.不要定义成员变量(使用局部变量)
2.使用prototype“多例”
3.使用ThreadLocal
/QGhurt/article/details/108089077
Spring如何处理线程并发问题?
在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
创建Bean的三种方式
第一种方式:使用默认构造函数创建。
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
第二种方式: 实例工厂的方法注入,使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
/*** 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)*/public class InstanceFactory {public IAccountService getAccountService(){return new AccountServiceImpl();}}<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean><bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
第三种方式:静态工厂的方法注入,使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
/*** 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)*/public class StaticFactory {public static IAccountService getAccountService(){return new AccountServiceImpl();}}<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
参考文章——Spring中bean的注入方式
Spring中的DI(依赖注入)
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="/schema/beans"xmlns:xsi="/2001/XMLSchema-instance"xsi:schemaLocation="/schema/beans/schema/beans/spring-beans.xsd"><!-- spring中的依赖注入依赖注入:Dependency InjectionIOC的作用:降低程序间的耦合(依赖关系)依赖关系的管理:以后都交给spring来维护在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明依赖关系的维护:就称之为依赖注入。依赖注入:能注入的数据:有三类基本类型和String类型其他bean类型(在配置文件中或者注解配置过的bean,ref)复杂类型/集合类型注入的方式:有三种第一种:使用构造函数提供第二种:使用set方法提供(常用)第三种:使用注解提供@Bean--><!--构造函数注入:使用的标签:constructor-arg标签出现的位置:bean标签的内部标签中的属性type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始name:用于指定给构造函数中指定名称的参数赋值 (常用)=============以上三个用于指定给构造函数中哪个参数赋值===============================value:用于提供基本类型和String类型的数据ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。--><bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"><constructor-arg name="name" value="泰斯特"></constructor-arg><constructor-arg name="age" value="18"></constructor-arg><constructor-arg name="birthday" ref="now"></constructor-arg><!-- 不能 value="1970-01-01"传值,这是个string类型,不是Date类型--></bean><!-- 配置一个日期对象 --><bean id="now" class="java.util.Date"></bean><!-- set方法注入更常用的方式涉及的标签:property出现的位置:bean标签的内部标签的属性name:用于指定注入时所调用的set方法名称value:用于提供基本类型和String类型的数据ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象优势:创建对象时没有明确的限制,可以直接使用默认构造函数(这个会导致使用默认构造函数构建对象时,不注入值也能构建,如果有一些值忘记set进去了没法注入进去,那么它对应的set方法就没有被执行)弊端:如果有某个成员必须有值,则获取对象是有可能set方法没有执行。--><bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2"><property name="name" value="TEST" ></property><property name="age" value="21"></property><property name="birthday" ref="now"></property></bean><!-- 复杂类型的注入/集合类型的注入用于给List结构集合注入的标签:list array set(能混用)用于个Map结构集合注入的标签:map props结构相同,标签可以互换--><bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3"><property name="myStrs"><array><value>111</value><value>222</value><value>333</value></array><!-- <set>--><!--<value>AAA</value>--><!--<value>BBB</value>--><!--<value>CCC</value>--><!-- </set>--></property><property name="myList"><!-- 用于给List结构集合注入的标签:list array set(能混用)--><list><value>111</value><value>222</value><value>333</value></list><!-- <array>--><!--<value>AAA</value>--><!--<value>BBB</value>--><!--<value>CCC</value>--><!-- </array>--></property><property name="mySet"><set><value>111</value><value>222</value><value>333</value></set><!-- <list>--><!--<value>AAA</value>--><!--<value>BBB</value>--><!--<value>CCC</value>--><!-- </list>--></property><property name="myMap"><!-- 用于个Map结构集合注入的标签:--><!-- map props--><!-- 结构相同,标签可以互换--><props><prop key="testC">ccc</prop><prop key="testD">ddd</prop></props></property><property name="myProps"><map><entry key="testA" value="aaa"></entry><entry key="testB"><value>BBB</value></entry></map></property></bean></beans>
循环依赖
循环依赖是什么?⭐
bean A依赖于另一个bean B时,bean B依赖于bean A:
当Spring上下文加载所有bean时,它会尝试按照它们完全工作所需的顺序创建bean。例如,如果我们没有循环依赖,如下例所示:
豆A→豆B→豆C.
Spring将创建bean C,然后创建bean B(并将bean注入其中),然后创建bean A(并将bean B注入其中)。
但是,当具有循环依赖时,Spring无法决定应该首先创建哪个bean,因为它们彼此依赖。
以setter方式构成的循环依赖,spring可以帮你解决,以构造器方式构成的循环依赖,spring无法搞定。
setter 注入和构造器注入的区别就在于创建bean的过程中,setter注入可以先用无参数构造方法返回bean实例,再注入依赖的属性,使用到了 Spring 的三级缓存,而constructor方式无法先返回bean的实例,必须先实例化它所依赖的属性,这样一来就会造成死循环所以会失败。
我们使用比较多的在属性上@Autowired的方式,在spring内部的处理中,与setter方法不太一样,但用此种方式循环依赖也可以解决,原理同上,只要能先实例化出来,提前暴露出来,就可以解决循环依赖的问题。
Spring 如何解决循环依赖?⭐
Spring使用了三级缓存解决了循环依赖的问题。在populateBean()给属性赋值阶段里面Spring会解析你的属性,并且赋值,当发现,A对象里面依赖了B,此时又会走getBean方法,但这个时候,你去缓存中是可以拿的到的。因为我们在对createBeanInstance对象创建完成以后已经放入了缓存当中,所以创建B的时候发现依赖A,直接就从缓存中去拿,此时B创建完,A也创建完。至此Bean的创建完成,最后将创建好的Bean放入单例缓存池中。
上面已经大概的解释了一下,现在来详细说说:
我们知道,Bean 的创建最为核心三个方法解释如下:
createBeanInstance
:实例化,其实也就是调用对象的构造方法实例化对象populateBean
:填充属性,这一步主要是对bean的依赖属性进行注入(@Autowired
)initializeBean
:回到一些形如initMethod
、InitializingBean
等方法
从对单例Bean的初始化可以看出,循环依赖主要发生在第二步(populateBean),也就是field属性注入的处理。
现在再来了解一下三级缓存:
singletonObjects
:第一级,单例缓存池。用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用earlySingletonObjects
:第二级。提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性的 bean)singletonFactories
:第三级,单例对象工厂缓存 。单例对象工厂的cache,存放 bean 工厂对象
了解完缓存就可以开始了解单例 Bean 的创建过程:
先从一级缓存singletonObjects中去获取。(如果获取到就直接return)如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。(如果获取到就直接return)如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。(如果获取到了就把这个 bean 从 singletonFactories 中移除,并且放进 earlySingletonObjects。其实也就是从三级缓存移动(是剪切)到了二级缓存)
解决循环依赖的关键就在这个三级缓存,下面给出 A,B 循环依赖的情况 Spring 是如何操作的:
使用context.getBean(A.class),为了获取容器内的单例A,若A不存在,就会走A这个Bean的创建流程,显然初次获取A是不存在的,所以开始创建 A开始实例化A(createBeanInstance,注意此处仅仅是实例化),并将它放进缓存(此时A已经实例化完成,已经可以被引用了)开始准备初始化A(populateBean):解析 A 的依赖发现依赖注入了 B(此时需要去容器内获取B)此时开始实例化 B,到了依赖注入B时,会通过getBean(B)去容器内找B。实例化B,并将其放入缓存。(此时B也能够被引用了)开始准备初始化B,发现依赖注入A(此时需要去容器内获取A)此处重要:初始化B时会调用getBean(A)去容器内找到A,上面我们已经说过了此时候因为A已经实例化完成了并且放进了缓存里,所以这个时候去看缓存里是已经存在A的引用了的,所以getBean(A)能够正常返回B初始化成功,return(注意此处return相当于是返回最上面的 getBean(B) 这句代码,回到了初始化A的流程中)。因为B实例已经成功返回了,因此最终A也初始化成功到此,B持有的已经是初始化完成的A,A持有的也是初始化完成的B
Spring常用的注解
第一步:在 applicationContext.xml 中引入命名空间 引入的命名空间,简单来说就是用来约束xml文件格式的。
第二步:在 applicationContext.xml 文件中引入注解扫描器
<context:component-scan base-package=“”></context:component-scan>
用于创建对象的
* 他们的作用就和在XML配置文件中编写一个<bean>标签实现的功能是一样的
*Component:
* 位置:类的前面 例如:
* @Service("accountService")
* public class AccountServiceImpl implements IAccountService {
*
* 作用:用于把当前类对象存入spring容器中
* 属性:
* value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
* Controller:一般用在表现层 * Service:一般用在业务层 * Repository:一般用在持久层
以上三个注解他们的作用和属性与Component是一模一样。
* 他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
用于注入数据的
* 他们的作用就和在xml配置文件中的bean标签中写一个<property>标签的作用是一样的
*Autowired:
* 作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
* 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
* 如果Ioc容器中有多个类型匹配时:
* 出现位置:
* 可以是变量上,也可以是方法上
* 细节:
* 在使用注解注入时,set方法就不是必须的了。
*Qualifier:
* 作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以(稍后我们讲)
* 属性:
* value:用于指定注入bean的id。
以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
* 另外,集合类型的注入只能通过XML来实现。
*
* Value
* 作用:用于注入基本类型和String类型的数据
* 属性:
* value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
* SpEL的写法:${表达式}
*
* 用于改变作用范围的
* 他们的作用就和在bean标签中使用scope属性实现的功能是一样的
* Scope
* 作用:用于指定bean的作用范围
* 属性:
* value:指定范围的取值。常用取值:singleton prototype
*
* 和生命周期相关 了解
* 他们的作用就和在bean标签中使用init-method和destroy-methode的作用是一样的
* PreDestroy
* 作用:用于指定销毁方法
* PostConstruct
* 作用:用于指定初始化方法
@Resource注解,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Resource 的使用来消除 set ,get方法。
@Autowired功能和注解 @Resource 一样,可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。只不过注解@Resource 是按照名称来进行装配,而@Autowired 则是按照类型来进行装配。
(它首先会根据类型去匹配,多个则按名称,空则会抛出异常。法二:@Autowired 和 @Qualifier(“名称”) 配合使用)
@Qualifier:将@Qualifier注解和@Autowire注解结合使用,指定需要装配的确切的bean。
@Required:表明bean的属性必须在配置的时候设置.
@Configuration 声明当前类是一个配置类
@Bean 注解的方法将会实例化、配置和初始化一个新对象,这个对象将由 Spring的 IoC 容器来管理, 作用与 元素类似
不同点:实现原理不同,@Configuration基于CGlib代理实现,@Component基于反射实现;
@Bean和@Component的区别
@Component 在类上使用,表示这是一个组件类,需要 Spring 为这个类创建 Bean
@Bean 在方法上使用,告诉 Spring 这个方法将返回一个 Bean 对象,需要把返回的对象注册到应用上下文中
@ImportResource:引用类@ComponentScan:自动扫描@EnableCaching && Cacheable: 开启注解式的缓存支持/缓存@Transactional:开启事务@Aspect && Poincut:切面和切点
Spring 中 注解@Autowired和@Resources的区别
@Autowired
@Autowired默认先按byType(类型)进行匹配,如果发现找到多个bean,则又按照byName方式进行匹配,如果还有多个,则报出异常。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。
@Qualifier:当一个接口有多个实现的时候,为了指名具体调用哪个类的实现。
@Resource
默认按byName(名称)自动注入。 既不指定name属性,也不指定type属性,则自动按byName方式进行查找。如果没有找到符合的bean,则回退为一个原始类型进行进行查找,如果找到就注入。 只是指定了@Resource注解的name,则按name后的名字去bean元素里查找有与之相等的name属性的bean。 只指定@Resource注解的type属性,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常。
@Autowired:默认按类型装配 b. @Resource:默认先按名称,找不到按类型 若要按指定名称:@Resource(“service”)
@Resource注解属性名表示按照属性名来查找类,找到匹配的类后,自动创建一个bean来存放对象,并注入属性。
@Autowired时先按照属性的类型进行查找类,如果有多个再找属性名,属性名还是有多个就报错
Spring中用到哪些设计模式?
1.工厂模式(BeanFactory中)BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
2.单例模式(Spring中默认bean为单例)
3.适配器模式 Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式(HandlerAdater)适配Controller
。
4.装饰者模式
5.代理模式(AOP中用到JDK动态代理)
6.观察者模式(listener的实现,例如ApplicationListener)Spring 事件驱动模型就是观察者模式很经典的一个应用。
7.策略模式(定义一系列的算法,把它们一个个的封装起来,并且使它们可以相互替换。在实例化对象时用到)
8.模板模式(jdbcTemplate)用来解决代码重复的问题。比如.RestTemplate,JmsTemplate,JpaTemplate。
9.观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现--ApplicationListener。
10.包装器设计模式: 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
Spring事务的实现方式和实现原理:
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
(1)Spring事务的种类:
spring支持编程式事务管理和声明式事务管理两种方式:
①编程式事务管理使用TransactionTemplate。
②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。
声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式,使业务代码不受污染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
(2)spring的事务传播行为:
spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。
我们通常使用的是前两种:REQUIRED和SUPPORTS.事务传播行为如下:
REQUIRED: Spring默认事务传播行为. 若当前没有事务,就新建一个事务;若当前已经存在一个事务中,加入到这个事务中.
增删改查操作均可用
SUPPORTS: 若当前没有事务,就不使用事务;若当前已经存在一个事务中,加入到这个事务中.查询操作可用
Spring的API设计很不错,基本上根据英文翻译就能知道作用:
required:必须的。说明必须要有事务,没有就新建事务。
required_new:必须新建事务。如果当前存在事务就挂起。
supports:支持。说明仅仅是支持事务,没有事务就非事务方式执行。
not_supported:不支持事务,如果存在事务就挂起。
mandatory:强制的。说明一定要有事务,没有事务就抛出异常。
never:绝不有事务。如果存在事务就抛出异常。
NESTED
:若当前存在事务,则在嵌套事务内执行;若当前没有事务,则执行REQUIRED
类似的操作
① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘
③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
(3)Spring中的隔离级别:
①ISOLATION_DEFAULT:这是个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。
②ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务可以看到这个事务未提交的数据。
③ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。
④ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。
⑤ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。
/QGhurt/article/details/107345971
解释一下Spring AOP里面的几个名词:
(1)切面(Aspect):被抽取的公共模块,可能会横切多个对象。在Spring AOP中,切面可以使用通用类(基于模式的风格)或者在普通类中以@AspectJ注解来实现。
(2)连接点(Join point):指方法,在Spring AOP中,一个连接点总是代表一个方法的执行。
(3)通知(Advice):在切面的某个特定的连接点(Join point)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
(4)切入点(Pointcut):切入点是指 我们要对哪些Join point进行拦截的定义。通过切入点表达式,指定拦截的方法,比如指定拦截add*、search*。
(5)引入(Introduction):(也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现IsModified接口,以便简化缓存机制。
(6)目标对象(Target Object):被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做被通知(adviced)对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。
(7)织入(Weaving):指把增强应用到目标对象来创建新的代理对象的过程。Spring是在运行时完成织入。
切入点(pointcut)和连接点(join point)匹配的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。切入点使得定位通知(advice)可独立于OO层次。例如,一个提供声明式事务管理的around通知可以被应用到一组横跨多个对象中的方法上(例如服务层的所有业务操作)。
参考文章
前置通知
环绕通知
Spring通知有哪些类型?
/qq_32331073/article/details/80596084
前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。 在方法前切入
正常返回通知[After returning advice]:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
异常返回通知[After throwing advice]:在连接点抛出异常后执行。
返回通知[After (finally) advice]:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
环绕通知[Around advice]:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
包围一个连接点的增强,如方法调用。这是最强大的一种增强类型。 环绕增强可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
(1)前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
(2)返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
(3)抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。
(4)后通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
(5)环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。
同一个aspect,不同advice的执行顺序:
①没有异常情况下的执行顺序:
around before advice
before advice
target method 执行
around after advice
after advice
afterReturning
②有异常情况下的执行顺序:
around before advice
before advice
target method 执行
around after advice
after advice
afterThrowing:异常发生
java.lang.RuntimeException: 异常发生
SpringMVC篇
springmvc执行流程
1). 用户发请求–>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制。
2).DispatcherServlet–>HandlerMapping,HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器,多个HandlerInterceptor拦截器)。
3).DispatcherServlet–>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器。
4).HandlerAdapter–>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理,并返回一个ModelAndView对象(包含模型数据,逻辑视图名)
5).ModelAndView的逻辑视图名–>ViewResolver,ViewResoler将把逻辑视图名解析为具体的View。
6).View–>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构
7).返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户。
1、用户发送请求至前端控制器DispatcherServlet
2、DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、处理器映射器找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、DispatcherServlet调用HandlerAdapter处理器适配器 (适配者模式)
5、HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、Controller执行完成返回ModelAndView
7、HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9、ViewReslover解析后返回具体View
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet响应用户
spring mvc 有哪些组件?
Spring MVC的核心组件:
DispatcherServlet:中央控制器,把请求给转发到具体的控制类Controller:具体处理请求的控制器HandlerMapping:映射处理器,负责映射中央处理器转发给controller时的映射策略ModelAndView:服务层返回的数据和视图层的封装类ViewResolver:视图解析器,解析具体的视图Interceptors :拦截器,负责拦截我们定义的请求然后做处理工作
1、前端控制器DispatcherServlet(不需要工程师开发),由框架提供(重要)
作用:Spring MVC 的入口函数。接收请求,响应结果,相当于转发器,中央处理器。有了 DispatcherServlet 减少了其它组件之间的耦合度。用户请求到达前端控制器,它就相当于mvc模式中的c,DispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet的存在降低了组件之间的耦合性。
2、处理器映射器HandlerMapping(不需要工程师开发),由框架提供
作用:根据请求的url查找Handler。HandlerMapping负责根据用户请求找到Handler即处理器(Controller),SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
3、处理器适配器HandlerAdapter
作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler 通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
4、处理器Handler(需要工程师开发)
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。 由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。
5、视图解析器View resolver(不需要工程师开发),由框架提供
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view) View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。 一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。
6、视图View(需要工程师开发)
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf...)
Spring为什么要结合使用HandlerMapping以及HandlerAdapter来处理Handler?
符合面向对象中的单一职责原则,代码架构清晰,便于维护,最重要的是代码可复用性高。如HandlerAdapter可能会被用于处理多种Handler。
SpringMVC常用注解
@Controller 用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller 对象。
@RequestController@RestController=@ResponseBody+@Controller意味着,该Controller的所有方法都默认加上了@ResponseBody。
@RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
@PathVariable用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数
@RequestMapping(value="/product/{productId}",method = RequestMethod.GET)
public String getProduct(@PathVariable(“productId”) String productId)
@PathVariable:用于接收路径参数,比如@RequestMapping(“/hello/{name}”)申明的路径,将注解放在参数中前,即可获取该值,通常作为Restful的接口实现方法。
/*** PathVariable注解:绑定url中的占位符。* Restful风格的URL:请求路径一样,可以根据不同的请求方式去执行后台的不同方法* 假如: @RequestMapping(value="/testPathVariable")多个方法前路径都一样 就可以用sid进行区分* <a href="anno/testPathVariable/10">testPathVariable</a> /10* * 使路径变得更加简洁;* 获得参数更加方便,框架会自动进行类型转换。* @return*/@RequestMapping("/testRestful/{p1}/{p2}")public String testRestful(@PathVariable int p1, @PathVariable int p2, Model model){int result = p1+p2;//Spring MVC会自动实例化一个Model对象用于向视图中传值model.addAttribute("msg", "结果:"+result);//返回视图位置return "testRestful";}@RequestMapping(value="/testPathVariable/{sid}")public String testPathVariable(@PathVariable(name="sid") String id){System.out.println("执行了...");System.out.println(id);return "success";}
@Responsebody 注解表示该方法的返回的结果直接写入 HTTP 响应正文(ResponseBody)中,一般在异步获取数据时使用;
在使用 @RequestMapping 后,返回值通常解析为跳转路径,加上 @Responsebody 后返回结果不会被解析为跳转路径,而是直接写入HTTP 响应正文中。例如,异步获取 json 数据,加上 @Responsebody 注解后,就会直接返回 json 数据。
@RequestBody 接收JSON数据
该注解用于读取 Request 请求的 body 部分数据。允许request的参数在request体中,而不是在直接连接在地址后面。(放在参数前)
@ResponseBody 将java对象转换成json格式的字符串,返回给浏览器。该注解用于将 Controller 的方法返回的对象,写入到 Response 对象的 body 数据区。 (返回值旁或方法上)
/*** 模拟异步请求响应:jsp在页面上引入jQuery以发送json数据,因此需要向服务器发起一个对jQuery的请 * 求* @RequestBody:服务器从前端接收到的json格式的数据解析成实体类型* @ResponseBody:服务器响应时将返回给浏览器的数据解析成json格式*作用:用于获取请求体内容。直接使用得到的是 key=value&key=value结构的数据(get方式不适用)* 属性:** required:是否必须有请求体,默认为true。 当取值为true时,get请求方式会报错。如果取值为 * false,get请求得到的是null* 获取到请求体的内容 要加上@RequestBody注解,:提取表单 post提交的所有内容*/@RequestMapping("/testAjax")public @ResponseBody User testAjax(@RequestBody User user){System.out.println("testAjax方法执行了...");// 客户端发送ajax的请求,传的是json字符串,后端把json字符串封装到user对象中System.out.println(user);// 做响应,模拟查询数据库user.setUsername("username: testAjaxObjet");user.setAge(19);// 做响应return user;}
@RequestParam 在SpringMVC后台控制层获取参数的方式主要有两种,一种是request.getParameter(“name”),另外一种是用注解@RequestParam直接获取。
/*** 作用:把请求中指定名称的参数给控制器形参赋值(即网页传参名称与方法中参数名不一致时可用)* 属性:value:请求参数中的名称* required:请求参数中是否必须提供此参数。默认值为true。表示必须提供,不提供会出错** RequestParm注解:把请求中的指定名称的参数传递给控制器中的形参赋值 将name的值传给形参username*/@RequestMapping("/testRequestParam")public String testRequestParam(@RequestParam(name="name") String username){System.out.println("执行了...");System.out.println(username);return "success";}
@SessionAttributes
/*** SessionAttributes的注解:* @SessionAttributes(value={"msg"}) // 该注解必须写在类之前,把msg=美美存入到session域对中* 作用:用于多次执行控制器方法间的参数共享* @return*/@RequestMapping(value="/testSessionAttributes")public String testSessionAttributes(Model model){System.out.println("testSessionAttributes...");// 底层会存储到request域对象中// 执行方法体...向隐式对象添加属性attribute_user,可以在jsp中通过 ${attribute_user} 获取到model.addAttribute("msg","美美");//存入一对键值对return "success";}/*** 获取值* @param modelMap* @return*/@RequestMapping(value="/getSessionAttributes")public String getSessionAttributes(ModelMap modelMap){//model接口的一个实现类System.out.println("getSessionAttributes...");String msg = (String) modelMap.get("msg");//取出msg的值System.out.println(msg);return "success";}
怎样在方法里面得到Request,或者Session?
答:直接在方法的形参中声明request,SpringMvc就自动把request对象传入。
15、如果想在拦截的方法里面得到从前台传入的参数,怎么得到?
答:直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。
如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象?
答:直接在方法中声明这个对象,SpringMvc就自动会把属性赋值到这个对象里面。
SpringMvc用什么对象从后台向前台传递数据的?
答:通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以通过el表达式拿到。
19、怎么样把ModelMap里面的数据放入Session里面?
答:可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key。
SpringMvc中函数的返回值是什么
返回值可以有很多类型,有String, ModelAndView,List,Set等,一般用String比较好,如果是AJAX请求,返回的可以是一个集合
SpringMvc怎么处理返回值的
SpringMvc根据配置文件中InternalResourceViewResolver(内部资源视图解析器)的前缀和后缀,用前缀+返回值+后缀组成完整的返回值
SpringMVC怎么样设定重定向和转发的
在返回值前面加"forward:"就可以让结果转发,譬如"forward:user.do?name=method4"
在返回值前面加"redirect:"就可以让返回值重定向,譬如"redirect:"
SpringMvc中有个类把视图和数据都合并的一起的,叫什么?
ModelAndView
怎么样把数据放入Session里面
可以声明一个request,或者session先拿到session,然后就可以放入数据,或者可以在类上面加上@SessionAttributes注解,里面包含的字符串就是要放入session里面的key
SpringMvc怎么和AJAX相互调用的
通过Jackson框架就可以把Java里面的对象直接转化成js可以识别的Json对象
如何解决POST请求中文乱码问题,GET的又如何处理呢?
(1)解决post请求乱码问题:
在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8;
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
(2)get请求中文参数出现乱码解决方法有两个:
①修改tomcat配置文件添加编码与工程编码一致,如下:
<ConnectorURIEncoding="utf-8"connectionTimeout="20000"port="8080"protocol="HTTP/1.1"redirectPort="8443"/>
②另外一种方法对参数进行重新编码:
StringuserName =newString(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8")
ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。
SpringMVC怎么样设定重定向和转发的?
(1)转发:在返回值前面加"forward:",譬如"forward:user.do?name=method4"
(2)重定向:在返回值前面加"redirect:",譬如"redirect:"
SpringMvc的控制器是不是单例模式,如果是,有什么问题,怎么解决?
答:是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写字段。
SpingMvc中的控制器的注解一般用那个,有没有别的注解可以替代?
答:一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替。
如果在拦截请求中,我想拦截get方式提交的方法,怎么配置?
答:可以在@RequestMapping注解里面加上method=RequestMethod.GET。
SpringMVC注解原理
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
SpringMvc里面拦截器是怎么写的:
有两种写法,一种是实现HandlerInterceptor接口,另外一种是继承适配器类,接着在接口方法当中,实现处理逻辑;然后在SpringMvc的配置文件中配置拦截器即可
<!-- 配置SpringMvc的拦截器 --><mvc:interceptors><!-- 配置一个拦截器的Bean就可以了 默认是对所有请求都拦截 --><bean id="myInterceptor" class="com.zwp.action.MyHandlerInterceptor"></bean><!-- 只针对部分请求拦截 --><mvc:interceptor><mvc:mapping path="/modelMap.do" /><bean class="com.zwp.action.MyHandlerInterceptorAdapter" /></mvc:interceptor></mvc:interceptors>
Mybatis篇
1、什么是Mybatis?
(1)Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。
(2)MyBatis 可以使用XML 或注解来配置和映射原生信息,将POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
(3)通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。
2、Mybaits的优点:
(1)基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
(2)与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
(3)很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
(4)能够与Spring很好的集成;
(5)提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
3、MyBatis框架的缺点:
(1)SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
(2)SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
4、MyBatis框架适用场合:
(1)MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
(2)对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。
5、MyBatis与Hibernate有哪些不同?
(1)Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。
(2)Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成果。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套sql映射文件,工作量大。
(3)Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate开发可以节省很多代码,提高效率。
6、#{}和${}的区别是什么?
#{}是预编译处理,${}是字符串替换。
Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
Mybatis在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止SQL注入,提高系统安全性。
7、当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
第1种: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
<select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”>select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};</select>
第2种: 通过<resultMap>来映射字段名和实体类属性名的一一对应的关系。
<select id="getOrder" parameterType="int" resultMap="orderresultmap">select * from orders where order_id=#{id}</select><resultMap type=”me.gacl.domain.order” id=”orderresultmap”><!–用id属性来映射主键字段–><id property=”id” column=”order_id”><!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–><result property = “orderno” column =”order_no”/><result property=”price” column=”order_price” /></reslutMap>
8、 模糊查询like语句该怎么写?
第1种:在Java代码中添加sql通配符。
string wildcardname = “%smi%”;list<name> names = mapper.selectlike(wildcardname);<select id=”selectlike”>select * from foo where bar like #{value}</select>
第2种:在sql语句中拼接通配符,会引起sql注入
string wildcardname = “smi”;list<name> names = mapper.selectlike(wildcardname);<select id=”selectlike”>select * from foo where bar like "%"${value}"%"</select>
9、通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
Dao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。
Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MapperStatement对象。
举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面 id 为 findStudentById 的 MapperStatement。
Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。
Mapper接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
10、Mybatis是如何进行分页的?分页插件的原理是什么?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
11、Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
第一种是使用<resultMap>标签,逐一定义数据库列名和对象属性名之间的映射关系。
第二种是使用sql列的别名功能,将列的别名书写为对象属性名。
有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
12、如何执行批量插入?
首先,创建一个简单的insert语句:
<insert id=”insertname”>insert into names (name) values (#{value})</insert>
然后在java代码中像下面这样执行批处理插入:
list<string> names = new arraylist();names.add(“fred”);names.add(“barney”);names.add(“betty”);names.add(“wilma”);// 注意这里 executortype.batchsqlsession sqlsession = sqlsessionfactory.opensession(executortype.batch);try {namemapper mapper = sqlsession.getmapper(namemapper.class);for (string name : names) {mapper.insertname(name);}mit();}catch(Exception e){e.printStackTrace();sqlSession.rollback();throw e;}finally {sqlsession.close();}
13、如何获取自动生成的(主)键值?
insert 方法总是返回一个int值 ,这个值代表的是插入的行数。
如果采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。
示例:
<insert id=”insertname” usegeneratedkeys=”true” keyproperty=”id”>insert into names (name) values (#{name})</insert>
name name = new name();name.setname(“fred”);int rows = mapper.insertname(name);// 完成后,id已经被设置到对象中system.out.println(“rows inserted = ” + rows);system.out.println(“generated key value = ” + name.getid());
14、在mapper中如何传递多个参数?
(1)第一种://DAO层的函数Public UserselectUser(String name,String area);//对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。<select id="selectUser"resultMap="BaseResultMap">select * fromuser_user_t whereuser_name = #{0} anduser_area=#{1}</select>(2)第二种: 使用 @param 注解:public interface usermapper {user selectuser(@param(“username”) string username,@param(“hashedpassword”) string hashedpassword);}然后,就可以在xml像下面这样使用(推荐封装为一个map,作为单个参数传递给mapper):<select id=”selectuser” resulttype=”user”>select id, username, hashedpasswordfrom some_tablewhere username = #{username}and hashedpassword = #{hashedpassword}</select>(3)第三种:多个参数封装成maptry{//映射文件的命名空间.SQL片段的ID,就可以调用对应的映射文件中的SQL//由于我们的参数超过了两个,而方法中只有一个Object参数收集,因此我们使用Map集合来装载我们的参数Map<String, Object> map = new HashMap();map.put("start", start);map.put("end", end);return sqlSession.selectList("StudentID.pagination", map);}catch(Exception e){e.printStackTrace();sqlSession.rollback();throw e; }finally{MybatisUtil.closeSqlSession();}
15、Mybatis动态sql有什么用?执行原理?有哪些动态sql?
Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接sql的功能。
Mybatis提供了9种动态sql标签:trim | where | set | foreach | if | choose | when | otherwise | bind。
16、Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
答:<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>,加上动态sql的9个标签,其中<sql>为sql片段标签,通过<include>标签引入sql片段,<selectKey>为不支持自增的主键生成策略标签。
17、Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;
原因就是namespace+id是作为Map<String, MapperStatement>的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
但是,在以前的Mybatis版本的namespace是可选的,不过新版本的namespace已经是必须的了。
18、为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
19、 一对一、一对多的关联查询 ?
20、MyBatis实现一对一有几种方式?具体怎么操作的?
有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次,通过在resultMap里面配置association节点配置一对一的类就可以完成;
嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另外一个表里面查询数据,也是通过association配置,但另外一个表的查询通过select属性配置。
<mapper namespace="com.lcb.mapping.userMapper"><!--association 一对一关联查询 --><select id="getClass" parameterType="int" resultMap="ClassesResultMap">select * from class c,teacher t where c.teacher_id=t.t_id and c.c_id=#{id}</select><resultMap type="com.lcb.user.Classes" id="ClassesResultMap"><!-- 实体类的字段名和数据表的字段名映射 --><id property="id" column="c_id"/><result property="name" column="c_name"/><association property="teacher" javaType="com.lcb.user.Teacher"><id property="id" column="t_id"/><result property="name" column="t_name"/></association></resultMap>
21、MyBatis实现一对多有几种方式,怎么操作的?
有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的collection节点配置一对多的类就可以完成;嵌套查询是先查一个表,根据这个表里面的 结果的外键id,去再另外一个表里面查询数据,也是通过配置collection,但另外一个表的查询通过select节点配置。
<!--collection 一对多关联查询 --><select id="getClass2" parameterType="int" resultMap="ClassesResultMap2">select * from class c,teacher t,student s where c.teacher_id=t.t_id and c.c_id=s.class_id and c.c_id=#{id}</select><resultMap type="com.lcb.user.Classes" id="ClassesResultMap2"><id property="id" column="c_id"/><result property="name" column="c_name"/><association property="teacher" javaType="com.lcb.user.Teacher"><id property="id" column="t_id"/><result property="name" column="t_name"/></association><collection property="student" ofType="com.lcb.user.Student"><id property="id" column="s_id"/><result property="name" column="s_name"/></collection></resultMap></mapper>
22、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
答: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,支持延迟加载的原理都是一样的。
23、Mybatis的一级、二级缓存:
1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置<cache/> ;
3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新。
24、什么是MyBatis的接口绑定?有哪些实现方式?
接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定,我们直接调用接口方法就可以,这样比起原来了SqlSession提供的方法我们可以有更加灵活的选择和设置。
接口绑定有两种实现方式,一种是通过注解绑定,就是在接口的方法上面加上@Select、@Update等注解,里面包含Sql语句来绑定;另外一种就是通过xml里面写SQL来绑定,在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。当Sql语句比较简单时候,用注解绑定,当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。
25、使用MyBatis的mapper接口调用时有哪些要求?
①Mapper接口方法名和mapper.xml中定义的每个sql的id相同;
②Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同;
③Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;
④Mapper.xml文件中的namespace即是mapper接口的类路径。
26、Mapper编写有哪几种方式?
第一种:接口实现类继承SqlSessionDaoSupport:使用此种方法需要编写mapper接口,mapper接口实现类、mapper.xml文件。
(1)在sqlMapConfig.xml中配置mapper.xml的位置
<mappers>
<mapper resource="mapper.xml文件的地址" />
<mapper resource="mapper.xml文件的地址" />
</mappers>
(2)定义mapper接口
(3)实现类集成SqlSessionDaoSupport
mapper方法中可以this.getSqlSession()进行数据增删改查。
(4)spring 配置
<bean id=" " class="mapper接口的实现">
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
第二种:使用org.mybatis.spring.mapper.MapperFactoryBean:
(1)在sqlMapConfig.xml中配置mapper.xml的位置,如果mapper.xml和mappre接口的名称相同且在同一个目录,这里可以不用配置
<mappers>
<mapper resource="mapper.xml文件的地址" />
<mapper resource="mapper.xml文件的地址" />
</mappers>
(2)定义mapper接口:
①mapper.xml中的namespace为mapper接口的地址
②mapper接口中的方法名和mapper.xml中的定义的statement的id保持一致
③Spring中定义
<bean id="" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="mapper接口地址" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
第三种:使用mapper扫描器:
(1)mapper.xml文件编写:
mapper.xml中的namespace为mapper接口的地址;
mapper接口中的方法名和mapper.xml中的定义的statement的id保持一致;
如果将mapper.xml和mapper接口的名称保持一致则不用在sqlMapConfig.xml中进行配置。
(2)定义mapper接口:
注意mapper.xml的文件名和mapper的接口名称保持一致,且放在同一个目录
(3)配置mapper扫描器:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="mapper接口包地址"></property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
(4)使用扫描器后从spring容器中获取mapper的实现对象。
27、简述Mybatis的插件运行原理,以及如何编写一个插件。
答:Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
编写插件:实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
MyBatis编程步骤是什么样的?
① 创建SqlSessionFactory
② 通过SqlSessionFactory创建SqlSession
③ 通过sqlsession执行数据库操作
④ 调用mit()提交事务
⑤ 调用session.close()关闭会话
Mybatis的常用标签或者注解用过哪些
@Select 简单查询 @Select(" Select * from sellers “)
@Results @Result 用来配置列名和属性 @Result(id = true, column = “id”, property = “id”),
@Insert 简单插入
@Insert(” insert into tb_user(name,sex,age) values(#{name},#{sex},#{age} " )
int saveUser(User user);
@Update 简单更新
@Delete 简单删除
@Delete("delete from sellers where id =#{id} ")
void deleteById(Integer id);
@Mapper
<if>
<choose,when,otherwise> 有点像 Java 中的 switch 语句。
<trim>标签 格式化的标记
<foreach> 对一个集合进行遍历, 通常是在构建 IN 条件语句的时候
<foreach collection="" item="" index="" open="" separator="" close=""></foreach>
bind标签 <bind name="" value="" />取别名
从架构设计的角度,可以将 MyBatis 分为哪几层?每层都有哪些主要模块?
mybatis架构自下而上分为基础支撑层、数据处理层、API接口层这三层。
基础支撑层,主要是用来做连接管理、事务管理、配置加载、缓存管理等最基础组件,为上层提供最基础的支撑。
数据处理层,主要是用来做参数映射、sql解析、sql执行、结果映射等处理,可以理解为请求到达,完成一次数据库操作的流程。
API接口层,主要对外提供API,提供诸如数据的增删改查、获取配置等接口。
SqlMapConfig.xml中配置有哪些内容?
SqlMapConfig.xml中配置的内容和顺序如下:
properties(属性)
settings(配置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
MyBatists的 resultType和resultmap的区别
接下来说说有关Mapper.xml配置文件中查询标签中关于返回值类型resultType与resultMap
1) resultType是直接表示返回类型的,而resultMap 指的是定义好了的id的,是定义好的resyltType的引用,但是resultType跟resultMap不能同时存在。
2) 当提供的返回类型属性是resultType时,MyBatis会将Map里面的键值对取出赋给resultType所指定的对象对应的属性。所以其实MyBatis的每一个查询映射的返回类型都是ResultMap,只是当提供的返回类型属性是resultType的时候,MyBatis会自动的给把对应的值赋给resultType所指定对象的属性。
3)resultType对应的是java对象中的属性,大小写不敏感, 如果放的是java.lang.Map,key是查询语句的列名,value是查询的值,大小写敏感;resultMap对应的是对已经定义好了id的resultType的引用,key是查询语句的列名,value是查询的值,大小写敏感;
4)使用resultType的时候,要保证结果集的列名与java对象的属性相同,而resultMap则不用, 而且resultMap可以用typeHander转换
5) resultMap 元素,它是 MyBatis 中最重要最强大的元素,它能提供级联查询,缓存等功能。
JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?
① 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在SqlMapConfig.xml中配置数据链接池,如:c3p0 DBCP使用连接池管理数据库链接。
② Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。sql与代码分离 可维护行变高。
③ 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决: Mybatis自动将java对象映射至sql语句。Mybatis 使用statement 的 paremterType 定义输入的参数类型
④ 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象。Mybatis 使用resultType 自动映射到pojo中解决了jdbc解析结果的麻烦
使用MyBatis的mapper接口调用时有哪些要求?
① Mapper接口方法名和mapper.xml中定义的每个sql的id相同
② Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同
③ Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
④ Mapper.xml文件中的namespace即是mapper接口的类路径。
mybatis是如何进行分页的?分页插件的原理是什么?
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
MyBatis 逻辑分页和物理分页的区别是什么?
逻辑分页是一次性查询很多数据,然后再在结果中检索分页的数据。这样做弊端是需要消耗大量的内存、有内存溢出的风险、对数据库压力较大。
物理分页是从数据库查询指定条数的数据,弥补了一次性全部查出的所有数据的种种缺点,比如需要大量的内存,对数据库查询压力较大等问题。
Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
第一种是使用标签,逐一定义列名和对象属性名之间的映射关系。第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe,Mybatis一样可以正常工作。
有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
通常一个xml映射文件,都会写一个Dao接口与之对应,请问这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法可以重载吗?为什么?
Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个标签,都会被解析为一个MappedStatement对象。
Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。
Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。
Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
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中的sqlSession、sqlSessionFactoryBuild和sqlSessionFactory的理解。
sqlSession:封装了对数据 增删改查的方法
sqlSession是通过sqlSessionFactory创建的
.sqlSessionFactory是通过sqlSessionFactoryBuild创建的
sqlSessionFactoryBuild 是创建sqlSessionFactory时使用的.一旦创建成功后就不需要sqlSessionFactoryBuild的,因为sqlSession是通过sqlSessionFactory创建的,可以可以当做工具类使用
sqlSessionFactory是一个接口, 类里重载了opensession的不同的方法使用范围是在整个运行范围内,一旦创建可以重复使用.可以当做单实例对象来管理
sqlSession是面向用户的一个操作数据库的接口 每个线程都应该有自己的sqlSession 并且sqlSession不可以共享. 线程是不安全的,打开一个sqlSession用完之后就要关闭
Mybatis一级二级缓存
1)一级缓存(mybatis默认开启一级缓存)
1.session级别缓存 2.每个session都有自己的一级缓存 3.每次sqlSession去执行commit操作(插入,更新,删除),(无论时候关闭session,会自动清除sqlSession中的一级缓存),这样做的目的是,防止在用户修改数据库后,又有其他用户做读取操作,结果读取到缓存中的过期数据,发生脏读现象。 4.session关闭后,该sqlSession中一级缓存中的内容将自动清除。
2)二级缓存二级缓存是mapper级别的,需要手动开启。 第一次调用mapper下的SQL去查询用户的信息,查询到的信息会存放代该mapper对应的二级缓存区域。 第二次调用namespace下的mapper映射文件中,相同的sql去查询用户信息,会去对应的二级缓存内取结果。 如果调用相同namespace下的mapepr映射文件中增删改sql,并执行了commit操作,此时会清空二级缓存中的内容,任何一个sqlsession执行提交操作都会清空二级缓存中的内容。
<setting name="cacheEnabled" value="true" />全局配置,仍然需要在具体的mapper.xml中开启二级缓存Cache在mapper.xml配置文件开启缓存:<cache/>
即使开启了全局二级缓存,但是对于某些操作频繁的业务(在开启二级情况下,默认使用二级缓存)为例防止出现脏读现象,我们可以为某些操作单独设置禁用二级缓存。 例如:
<select id="getByName" parameterType="string" resultType="com.ncs.entity.Celebrity" useCache="false">select * from celebrity<where><include refid="concatName"></include></where></select>
对于update,insert,delete操作一般在commit提交后都需要刷新缓存,可以设置flushCahce="true"(默认为true)来刷新缓存(清空二级缓存),避免数据库脏读。 例子:
<insert id="insertCelebrity" parameterType="com.ncs.entity.Celebrity" flushCache="true"><!--如果使用自增长的主键,自增长的主键从1开始,且需要返回自增长的主键属性值给到原来保存成功的对象上 --><selectKey keyProperty="c_id" order="AFTER"resultType="java.lang.Integer">select LAST_INSERT_ID()</selectKey>insert into celebrity(c_id,c_name) values(#{c_id},#{c_name})</insert>
二级缓存重要参数 1.flushinterval(刷新时间间隔) 2.size(缓存条目数,hashMap的大小) 二级缓存的使用场景 1.对于访问次数多,但是实时性要求不高的查询采用二级缓存, 2.对于查询非常消耗时间的查询使用二级缓存,提高系统效率 3.可以和定时刷新(清空)缓存 flushcache结合使用 mybatis缓存的局限性 1.假如二级缓存内已经存在100万条商品的缓存数据,当这百万种商品中任何一种发生信息改变(update,delete,add),那么系统自动清空二级缓存,然后这百万条缓存数据就没了,用户再发出对于这百万条数据的查询时,就需要重新查询数据库。
Mybatis缓存局限性的解决方案:1.ehcache是一个分布式缓存框架,和redis,memcached,ehcache一类都为分布式缓存(将缓存数据集中管理, 这里说的集中管理不是单机的意思,这里也是一个集群的管理(例如:redis集群)) 2.引入ehcache jar包<!--分布式缓存框架ehcache --><dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache-core</artifactId><version>${ehcache}</version></dependency><!--mybatis整合ehcache --><dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>${mybatis-ehcache}</version></dependency>3.配置cache使用,在mapper文件内配置,使用eache<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache> 4.在classpath路径下加入ehcache.xml配置文件
Mybatis的#{}号和${}符
#{}是预编译处理,${}是字符串替换。 Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来 赋值; Mybatis 在处理${}时,就是把${}替换成变量的值。 使用#{}可以有效的防止 SQL 注入,提高系统安全性。