SSM面试题
- SpringMVC
- Spring面试题
- Mybatis
- 1. Mybatis概念
- 2. Mybatis的优缺点
- 3. #{}和${}的区别是什么
- 4. Mapper接口中的方法为什么不能重载
- 5. MyBatis不同xml映射文件中的id是否可以重复呢
- 6. Mybatis是如何进行分页的?分页插件的原理是什么?
- 7. Mybatis的插件运行原理
- 8. Mybaits延迟加载
- 9. Mybatis一级缓存与二级缓存
- 10. Mybatis sql执行结果封装返回目标对象的方式
- 11.Mybatis动态sql
- 12. Mybatis的mapeper接口调用时有哪些要求
- 13. 模糊查询like语句写法
- 14. 如何获取自动生成的(主)键值
- 15. 在mapper中如何传递多个参数
- 16. Mybatis一对一,一对多的关联查询
- 17. Mybatis的SQL执行流程
SpringMVC
1. 什么是SpringMVC?
- Spring MVC 是spring框架的一部分(子框架), 是实现对Servlet技术进行封装。
- MVC全名是Model View Controller,是模型(model)-视图(view)-控制器 (controller)的缩写,它是一种软件设计典范,是一种软件架构设计分层模式。
Model(模型)
是应用程序中用于处理应用程序数据逻辑的部分。
-View(视图)
是应用程序中处理数据显示的部分。Controller(控制器)
是应用程序中处理用户交互的部分。
最典型的MVC就是JSP + servlet + javabean的模式。
对SpringMVC的理解?
- Spring MVC是一个基于MVC架构的用来简化web应用程序开发的应用开发框架,它是Spring的一个模块,无需中间整合层来整合 ,它和Struts2一样都属于表现层的框架。在web模型中,MVC是一种很流行的框架,通过把Model,View,Controller分离,把较为复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
2. SpringMVC 的执行流程?
- 用户点击某个请求路径,发起一个request请求,此请求会被前端控制器处理.
- 前端控制器请求处理器映射器取查找
Handle
,可以依据注解或者XML配置取查找.- 处理器映射器根据配置找到相应的Handle(可能包含若干个Interceptor拦截器),返回给前端控制器.
- 前端控制器请求处理器适配器取执行相应的Handle处理器(常称为Controller).
- 处理器适配器执行Handle处理器.
- Handle处理器执行完毕之后返回给处理器适配器一个ModeAndView对象(SpringMVC底层对象,包括Model数据模型和View视图信息).
- 处理器适配器接收到Handle处理器返回的ModeAndView后,将其返回给前端控制器.
- 前端控制器接收到ModelAndView后,会请求视图解析器(ViewResolver)对视图进行解析.
- 视图解析器根据View信息匹配到相应的视图结果,返回给前端控制器.
- 前端控制器收到View具体视图后,进行视图渲染,将Mode中的模型数据填充到View视图中的request域,生成最终的视图(View).
- 前端控制器想用户返回请求结果.
3. @Autowired 与@Resource的区别
- @Resource的作用相当于@Autowire,只不过@Autowire
按byType自动注入
,而@Resource默认按ByName自动注入
.- @Resource有两个属性比较重要,分别是
name和type
,Spring将@Resource注解的name属性解析为bean的名字
,而type属性则解析为bean的类型
,所以如果使用name属性,则使用ByName的自动注入策略,而是用type属性时则使用byType自动注入策略.如果既不指定name也不指定type属性,这时将通过反射机制
使用byName
自动注入策略.
@Resource的装配顺序
- 如果
同时指定了name和type
,则从Spring上下文中找到唯一匹配的bean
进行装配,找不到则抛异常.- 如果
指定了name
,则从Spring上下文中查找名称(id)匹配的bean进行装配
,找不到则抛出异常.- 如果
指定了type
,则从Spring上下文中找到类型匹配的唯一bean进行装配
,找不到或者找到多个,都会抛出异常.- 如果
既没有指定name,又没有指定type
,则自动按照byName方式进行装配
,如果没有匹配到,则回退为一个原始类型进行匹配,如果匹配到则自动装配.
@Autowired 与@Resource的区别
- @Autowired与@Resource都可以用来装配bean,都可以写在
字段上,或者setter方法上
. - @Autowired默认
按照类型进行装配
(该注解属于Spring),默认情况下要求依赖对象必须存在
,如果要允许null值,可以设置它的required属性为false
,例如@Autowired(required = false)
,如果我们想使用名称装配可以结合@Qualifier注解进行使用
,如下:
@Autowired () @Qualifier ( "baseDao" )
private UserDao userDao ;
- @Atuowired是根据类型进行自动装配的.如果当Spring上下文存在不止一个个UserDao类型的bean时,就会抛出BeanCreationException异常,如果Spring上下文中不存在UserDao类型的bean,也会抛出BeanCreationException异常,我们可以使用@Qualifier配合@Autowired来解决这些问题.
- @Resource(该注解属于J2EE),默认
按照名称进行装配
,名称可以通过name属性进行指定,如果没有指定name属性
,当注解写在字段上
时,默认取字段名称进行名称查
找,如果注解写在setter方法
上默认取属性名进行装配
.当找不到名称匹配的bean时才按照类型进行装配
.但是需要注意的是,如果name属性一旦指定,就只会按照名称进行匹配
.
@Resource (name= "baseDao" )
private UserDao userDao ;
推荐使用
:@Resource注解在字段上,这样就不用写setter方法了,并且这个注解是属于J2EE的,减少了与spring的耦合。这样代码看起就比较优雅
通常情况一个Bean的注解写错了,会报下面这些错误,最为常见,
No bean named 'user' is defined
,这个表示没有找到被命名为user的Bean,通俗的说,就是名字为user的类型,以及它的子类型,出现这个错误的原因就是注入时候的类型名字为user,而搜索的时候找不到,也就是说可能那个搜索的类型,并没有命令为user,解决办法就是找到这个类型,去命令为user,
下面这个错误也常见,
No qualifying bean of type [com.service.UserService] found for dependency:
这个错误的原因就是类型上面没有加@Service这个注入,不仅仅是@Service,如果是其他层也会出现这个错误,这里我是以Service为例子说明,如果是DAO层就是没有加@Repository,Controller层,则是没有加@Controller。
Spring面试题
1. 什么是Spring?
Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。主要包括以下七个模块:
Spring Context
: 提供框架式的Bean访问方式,以及企业级功能(JNDI,定时任务);Spring Core
: 核心类库,所有的功能都依赖于该类库,提供IOC和DI服务.Spring AOP
: AOP面向切面服务.Spring Web
: 提供了基本的面向Web的综合特性,提供对常用框架与Struts2的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器.Spring MVC
: 提供面向WEB应用的Model-View-Controller,即MVC实现.Spring DAO
: 对JDBC的抽象封装,简化了数据访问异常的处理,并提供了统一管理JDBC事务.Spring ORM
: 对现有的ORM框架的支持.
2. Spring IOC与DI的理解?
什么是IOC?
- IOC,Inversion of Control,控制反转,指将对象的控制权转移给Spring框架,由 Spring 来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系
- 对于某个具体的对象而言,以前是由自己控制它所引用对象的生命周期,而在IOC中,所有的对象都被 Spring 控制,控制对象生命周期的不再是引用它的对象,而是Spring容器,由 Spring 容器帮我们创建、查找及注入依赖对象,而引用对象只是被动的接受依赖对象,所以这叫控制反转
2.1 依赖注意的几种方式
- 构造函数注入
- setter注入
- 接口注入
在 Spring Framework 中,仅使用构造函数和 setter 注入
- 构造函数注入
如果组件只有一个有参构造器,这个有参构造器的@Autowried可以省略,参数位置的组件还是可以自动从容器获取
@Component
@Data
public class Role{
private User user;
@Autowired
public Role(User user) {
this.user= user;
}
//也可以这样写:
public Role(@Autowired User user) {
this.user= user;
}
//也可以不写 @Autowired
}
- setter方式注入
//User用户类
package com.spring.bean;
@Component
public class User{
public User() {
System.out.println("user...constructor");
}
}
//Role角色类
package com.spring.bean;
//默认加在ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作
@Component
@Data
public class Role{
private User user;
public User getUser() {
return user;
}
//标注在方法,Spring容器创建当前对象,就会调用方法,完成赋值;
//方法使用的参数,自定义类型的值从ioc容器中获取
@Autowired
public void setUser(User user) {
this.user= user;
}
//也可以这样写:
public void setUser (@Autowired User user) {
this.user= user;
}
}
//配置类
@Configuration
@ComponentScan({com.spring.bean"})
public class MainConifgOfAutowired {
}
- 作为Bean的参数注入
//@Bean标注的方法创建对象的时候,方法参数的值从容器中获取
@Bean
public Role authority(User user){
Role role= new Role();
role.setUser(user);
return role;
}
什么是DI依赖注入?
IoC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(Dependency Injection,依赖注入)来实现的,即应用程序在运行时依赖 IoC 容器来动态注入对象所需要的外部依赖。而 Spring 的 DI 具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性
3. bean加载过程(生命周期)
详细加载流程可参考: Spring的Bean加载流程_张维鹏的博客-CSDN博客,此处我们只对bean流程进行简单总结.
- Spring容器可以管理Singleton作用域的Bean的生命周期,在此作用域下,Spring能够精确地知道Bean何时把被创建,何时初始化,以及何时被销毁.
- 对于Prototype作用域的Bean,Spring只负责创建,当容器创建了Bean的实例后,Bean的实例就交给客户端代码管理,Spring容器将不再跟踪其生命周期.每次客户端请求Prototype作用域的Bean时,Spring容器都会创建一个新的实例,并且不会管那些被配置成Prototykpe作用域的Bean的生命周期.
- 了解Spring生命周期的意义就在于可以利用Bean在其存活期间的指定时刻完成一些相关的操作.这种时刻可能有很多,但是一般情况下,
会在Bean被初始化后和被销毁前执行一些操作
.- 在Spring中,Bean的生命周期是一个很复杂的执行过程,我们可以利用Spring提供的方法定制Bean的创建过程.
- 当一个Bean被加载到Spring容器时,它具有了自己的生命周期,而Spring容器在保证一个Bean能够使用之前,会进行很多的工作,Spring容器中的Bean的生命周期如图所示:
Bean生命周期执行过程如下描述:- 根据配置情况调用Bean的构造方法或者工厂方法实例化Bean.
- 利用依赖注入(DI)完成Bean中所有属性值的配置注入.
- 如果Bean实现了BeanNameAware接口,则Spring调用Bean的setBeanName()方法传入当前Bean的id值.
- 实现BeanNameAware接口后,重写setBeanName方法,可以对单个Bean进行扩展修改;
- 如果Bean实现了BeanFactoryAware接口,则Spring调用setBeanFactory()方法传入当前工厂实例的引用.
- 实现BeanFactoryAware接口后,重写setBeanFactory方法,可以对bean工厂中的所有Bean进行扩展修改(属性修改);
- 如果Bean实现了ApplicationContextAware接口,则Spring调用setApplicationContext()方法传入当前ApplicationContext实例的引用.
- 实现ApplicationContextAware接口后,重写setApplicationContext方法后,可以对整个容器进行扩展修改(Spring容器进行设置);
这几个接口的执行顺序分别是BeanNameAware->BeanFactoryAware->ApplicationContextAware
- 如果BeanPostProcessor和Bean关联,则Spring将调用该接口的预初始化方法postProcessAfterInitialization().此时,Bean已经可以被应用系统使用了.
- BeanPostProcessor接口的前置和后置处理,是在Aware接口之后调用
- 很重要的一点,需要将BeanPostProcessor接口实现类声明为bean,使用配置或者使用@Component注解,不然BeanPostProcessor不起作用
- 如果在中指定了该bean的作用范围为scope=“singleton”,则将该Bean放入Spring IOC的缓冲池中,将触发Spring对该Bean的生命周期管理,如果在中指定了Bean的作用范围为scope=“prototype”,则将该Bean交给调用者,调用者管理该Bean的生命周期,Spring不在管理该Bean.
- 如果Bean实现了DisposableBean接口,则Spring会调用destory()方法将Spring中的Bean销毁,如果在配置文件中通过destory-method属性指定了Bean的销毁方法,则Spring将调用改方法对Bean进行销毁.
Spring 为 Bean 提供了细致全面的生命周期过程,通过实现特定的接口或 的属性设置,都可以对 Bean 的生命周期过程产生影响。虽然可以随意配置 的属性,但是建议不要过多地使用 Bean 实现接口,因为这样会导致代码和 Spring 的聚合过于紧密。
4. Spring的AOP理解:
OOP面向对象
,运行开发者定义纵向的关系,但并不适用与定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用.AOP面向切面
,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块
,这个模块被命名为切面(Aspect)
,减少系统中的重复代码,降低了模块间的耦合度,提供系统的可维护性,可用于权限认证,日志,事务处理
.- AOP实现了关键在于
代理模式
,AOP代理主要分为静态代理和动态代理.静态代理的代表是AspectJ,动态代理则以Spring AOP为代表.
- AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
- Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
AOP的基本术语
- Advice(通知/增强处理):AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法。
- Target Object(目标对象):指所有被通知的对象,也被称为被增强对象。如果AOP框架采用的是动态的AOP实现,那么该对象就是一个被代理对象。
- Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。
- Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。
Spring AOP中的动态代理主要有两种方式
JDK动态代理
:只提供接口
的代理,不支持类的代理,要求被代理类实现接口
。JDK动态代理的核心是InvocationHandler接口和Proxy类,在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起- InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理对象; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
CGLIB代理
:如果被代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
5. spring循环依赖
详细介绍参考:Spring如何解决循环依赖问题
6. spring事务的实现方式和实现原理
Spring事务的本质其实就是
数据库对事务的支持
,没有数据库的事务支持,spring是无法提供事务功能的。Spring只提供统一事务管理接口
,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过 redo log 和 undo log实现的
。Spring会在事务开始时,根据当前环境中设置的隔离级别,调整数据库隔离级别,由此保持一致。
Spring事务的种类:
spring支持编程式事务管理和声明式事务管理两种方式:
编程式事务管理
使用TransactionTemplate。声明式事务管理
建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
- 声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到
方法级别
,无法做到像编程式事务那样可以作用到代码块级别
。
spring的事务传播机制:
spring事务的传播机制说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。事务传播机制实际上是使用简单的ThreadLocal实现的,所以,如果调用的方法是在新线程调用的,事务传播实际上是会失效的。
- PROPAGATION_REQUIRED:(默认传播行为)如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务。
- PROPAGATION_REQUIRES_NEW:无论当前存不存在事务,都创建新事务进行执行。
- PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务;如果当前不存在事务,就以非事务执行。‘
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按REQUIRED属性执行。
- PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务;如果当前不存在事务,就抛出异常。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
Spring中的隔离级别:
- ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
- ISOLATION_READ_UNCOMMITTED:读未提交,允许事务在执行过程中,读取其他事务未提交的数据。
- ISOLATION_READ_COMMITTED:读已提交,允许事务在执行过程中,读取其他事务已经提交的数据。
- ISOLATION_REPEATABLE_READ:可重复读,在同一个事务内,任意时刻的查询结果都是一致的。
- ISOLATION_SERIALIZABLE:所有事务逐个依次执行。
7. Spring 框架中都用到了哪些设计模式
Spring设计模式的详细使用案例可以阅读这篇文章:Spring中所使用的设计模式_张维鹏的博客-CSDN博客_spring使用的设计模式
工厂模式
:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象单例模式
:Bean默认为单例模式策略模式
:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略代理模式
:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术模板方法
:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate适配器模式
:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller观察者模式
:Spring事件驱动模型就是观察者模式的一个经典应用。桥接模式
:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库
8. spring事务失效场景
数据库不支持事务
- Spring事务生效的前提是所连接的数据库要支持事务,如果底层的数据库都不支持事务,则Spring的事务肯定会失效。例如,如果使用的数据库为MySQL,并且选用了MyISAM存储引擎,则Spring的事务就会失效。
事务方法未被Spring管理
- 如果事务方法所在的类没有加载到Spring IOC容器中,也就是说,事务方法所在的类没有被Spring管理,则Spring事务会失效,例如service类上没有标注@Service注解.
public class ProductService {
@Autowired
private ProductDao productDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateProductStockCountById(Integer stockCount, Long id){
productDao.updateProductStockCountById(stockCount, id);
}
}
方法没有被public修饰
- 如果事务所在的方法没有被public修饰,此时Spring的事务会失效,在事务代理对象中有判断代理的方法必须是public,否则返回null.
@Service
public class ProductService {
@Autowired
private ProductDao productDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void updateProductStockCountById(Integer stockCount, Long id){
productDao.updateProductStockCountById(stockCount, id);
}
}
源码中有判断:
if(allowPublicMethodsOnly) && !Modifier.isPublic(method.getModifiers())){
return null;
}
同一类中方法调用
- 如果同一个类中的两个方法分别为A和B,方法A上没有添加事务注解,方法B上添加了 @Transactional事务注解,方法A调用方法B,则方法B的事务会失效
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private ProductDao productDao;
public void submitOrder(){
//生成订单
Order order = new Order();
long number = Math.abs(new Random().nextInt(500));
order.setId(number);
order.setOrderNo("order_" + number);
orderDao.saveOrder(order);
//减库存
this.updateProductStockCountById(1, 1L);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateProductStockCountById(Integer stockCount, Long id){
productDao.updateProductStockCountById(stockCount, id);
}
}
未配置事务管理器
- 如果在项目中没有配置Spring的事务管理器,即使使用了Spring的事务管理功能,Spring的事务也不会生效。
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
方法的事务传播类型不支持事务
\
- 如果内部方法的事务传播类型为不支持事务的传播类型,则内部方法的事务在Spring中会失效。
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private ProductDao productDao;
@Transactional(propagation = Propagation.REQUIRED)
public void submitOrder(){
//生成订单
Order order = new Order();
long number = Math.abs(new Random().nextInt(500));
order.setId(number);
order.setOrderNo("order_" + number);
orderDao.saveOrder(order);
//减库存
this.updateProductStockCountById(1, 1L);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateProductStockCountById(Integer stockCount, Long id){
productDao.updateProductStockCountById(stockCount, id);
}
}
//由于updateProductStockCountById()方法的事务传播类型为NOT_SUPPORTED,不支持事务,则updateProductStockCountById()方法的事务会在Spring中失效。
不正确的捕获异常
- 不正确的捕获异常也会导致Spring的事务失效,示例如下
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private ProductDao productDao;
@Transactional(propagation = Propagation.REQUIRED)
public void submitOrder(){
//生成订单
Order order = new Order();
long number = Math.abs(new Random().nextInt(500));
order.setId(number);
order.setOrderNo("order_" + number);
orderDao.saveOrder(order);
//减库存
this.updateProductStockCountById(1, 1L);
}
@Transactional(propagation = Propagation.REQUIRED)
public void updateProductStockCountById(Integer stockCount, Long id){
try{
productDao.updateProductStockCountById(stockCount, id);
int i = 1 / 0;
}catch(Exception e){
logger.error("扣减库存异常:", e.getMesaage());
}
}
}
//updateProductStockCountById()方法中使用try-catch代码块捕获了异常,即使updateProductStockCountById()方法内部会抛出异常,
//但也会被catch代码块捕获到,此时updateProductStockCountById()方法的事务会提交而不会回滚,
//并且submitOrder()方法的事务会提交而不会回滚,这就造成了Spring事务的回滚失效问题。
错误的标注异常类型
- 如果在@Transactional注解中标注了错误的异常类型,则Spring事务的回滚会失效
@Transactional(propagation = Propagation.REQUIRED)
public void updateProductStockCountById(Integer stockCount, Long id){
try{
productDao.updateProductStockCountById(stockCount, id);
}catch(Exception e){
logger.error("扣减库存异常:", e.getMesaage());
throw new Exception("扣减库存异常");
}
}
//在updateProductStockCountById()方法中捕获了异常,并且在异常中抛出了Exception类型的异常,此时,updateProductStockCountById()方法事务的回滚会失效。
//为何会失效呢?这是因为Spring中对于默认回滚的事务异常类型为RuntimeException,上述代码抛出的是Exception异常。
//默认情况下,Spring事务中无法捕获到Exception异常,所以此时updateProductStockCountById()方法事务的回滚会失效。
//此时可以手动指定updateProductStockCountById()方法标注的事务异常类型,如下所示。
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
//这里,需要注意的是:Spring事务注解@Transactional中的rollbackFor属性可以指定 Throwable 异常类及其子类。
9.@Autowired 和@Resource的区别
使用@Autowired 注解,会按类型装配,如果有多个同类型的 bean,会抛出异常。
使用@Resource 注解,会按名称装配,如果名称不存在,会使用类型装配。
如果你的项目中没有多个同类型的bean,那么@Autowired和@Resource是可以互换使用的,如果有多个同类型的bean,那么就要使用
spring的注解,一个属性,默认的注入方式为byType(根据类型进行匹配),type无法分辨时,可以根据name分辨,变量名称要与Bean名称一致,也可以通过@Qualifier 注解来显式指定Bean名称。
@Resource进行指定名称注入。
如果有多个同类型的bean,但是你没有使用@Resource进行名称指定,就会抛出异常,所以在使用@Autowired注解时,要确保只有一个同类型的bean。
什么是同类型的Bean
在上面的示例中,同类型的bean指的是有多个类型为MyDao的bean存在于Spring容器中,如果使用@Autowired注解进行注入的话,Spring容器并不知道应该注入哪一个MyDao类型的bean,因此会抛出异常。
换句话说就是如果你在项目中有多个类都是实现了同一个接口或者继承了同一个类,并且这些类都被标记为了@Service,@Repository等等,并且你在其他地方使用了这个接口或者类进行了注入,那么就会出现多个同类型的bean的情况。
比如说你有两个类A和B都是实现了接口I,并且都是被标记为了@Service的,那么当你在其他地方使用I进行注入的时候,就会出现多个同类型的bean的情况。
Mybatis
1. Mybatis概念
- Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,加载驱动,创建连接,创建statement等繁杂的过程,开发者开发时只需要关注如何编写SQL语句,可以严格控制sql执行性能,灵活度较高.
- 作为一个半ORM框架,Mybatis可以使用XML或者注解来配置和映射原生信息,将POJO映射成数据库中的记录,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集.
- 称Mybatis是半自动ORM映射工具,是因为在查询关联对象或关联集合对象时,需要手动编写sql来完成。不像Hibernate这种全自动ORM映射工具,Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取。
- 通过XML文件或注解方式将要执行的各种statement配置起来,并通过java对象和statement的动态参数进行映射生产最终执行的sql语句,最后由Mybatis框架执行sql并将结果映射为java对象返回.
- 由于Mybatis专注于sql本身,灵活度较高,所以比较适合对性能的要求很高,或者需求变化较多的项目,如互联网项目.
2. Mybatis的优缺点
优点:
- 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接
- 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
- 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)
- 能够与Spring很好的集成;
- 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
缺点
- SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
- SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
3. #{}和${}的区别是什么
- #{}是预编译处理,${}是字符串替换
- Mybatis在处理#{}时,会将sql中的#{}替换成为?号,调用PreparedStatement的set方法来赋值,而在处理 时 , 是把 {}时,是把 时,是把{}替换为变量的值.
- 使用#{}可以有效的防止SQL注入,提高系统安全性,因为一个#{}被解析为一个参数占位符,原因在于:
预编译机制
,预编译完成之后,SQL的结构已经固定,即便用户输入非法参数,也不会对SQL的结构产生影响,从而避免了潜在的安全风险,而${}仅仅为一个纯粹的String替换
.注意
:在Mybatis排序使用order by动态传参时,使用${}而不用#{}.
4. Mapper接口中的方法为什么不能重载
Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象 MappedProxy,代理对象会拦截接口方法,根据类的全限定名+方法名,唯一定位到一个MapperStatement并调用执行器执行所代表的sql,然后将sql执行结果返回。
Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。
- Dao接口,就是Mapper接口。
- 接口的全限名,就是映射文件中的namespace的值;
- 接口的方法名,就是映射文件中Mapper的Statement的id值;
- 接口方法内的参数,就是传递给sql的参数。
当调用接口方法时,通过
“接口全限名+方法名”
拼接字符串作为key值,可唯一定位一个MapperStatement
,因为在Mybatis中,每一个SQL标签,都会被解析为一个MapperStatement对象
。
例如:
com.mybatis.mappers.UserDao.findUserById,可以唯一找到namespace为com.mybatis.mappers.UserDao下面 id 为 findUserById 的 MapperStatement
5. MyBatis不同xml映射文件中的id是否可以重复呢
- 不同的Xml映射文件,如果
配置了namespace
,那么id可以重复
;如果没有配置namespace
,那么id不能重复
;原因就是namespace+id是作为Map的key使用的
,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖
。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。- 备注:在旧版本的Mybatis中,namespace是可选的,不过新版本的namespace已经是必填项了。
6. Mybatis是如何进行分页的?分页插件的原理是什么?
- Mybatis使用
RowBounds
对象进行分页,它是针对ResultSet结果集
执行的内存分页
,而非物理分页
。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。- 分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,
在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数
。
在生产环境中,不适合直接使用Mybatis原有的RowBounds对象进行分页,而是使用如下两种方案
- 在SQL内手动书写数据库分页的参数来完成分页功能,如下:
select * from t_userInfo limit #{first},#{pageSize}
- 也可以使用开源的分页插件来完成数据库分页,如:
- Mybatis-PageHelper
- Mybatis-Plus
注意:
由于分页插件是自动添加limit
拼接,往往针对一些复杂SQL,无法达到最大的SQL性能,更推荐手写分页SQL,也就是第一种方式.
分页插件的原理
- 分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义的分页插件.在插件的拦截方法内,拦截待执行的SQL,然后重写SQL,根据dialect方言,添加对应的物理分页语句和物理分页参数.
select * from t_userInfo 拦截SQL后重写为:select * from t_userInfo limit 0,10;
7. Mybatis的插件运行原理
- Mybatis仅可以编写针对ParameterHandler,ResultSetHandler,StatementHandler,Exector这4中接口的插件,
Mybatis使用JDK的动态,为需要拦截的接口生产代理对象以实现接口方法拦截功能
,每当执行这4种接口对象的方法时,就会进行拦截方法,具体就是InvocationHandler的invoke()方法
,当然,只会拦截哪些你指定需要拦截的方法.- 编写插件:实现Mybatis的Interceptor接口并覆写intercept()方法,然后再给插件编写注解,指定要拦截哪一个接口的哪些方法即可,最后在配置文件中配置你编写的插件即可.
MyBatis 插件的运行是基于 JDK 动态代理 + 拦截器链实现
Interceptor 是拦截器
,可以拦截 Executor, StatementHandle, ResultSetHandler, ParameterHandler 四个接口InterceptorChain 是拦截器链
,对象定义在 Configuration 类中Invocation 是对方法、方法参数、执行对象和方法的执行的封装
- 写一个插件,分三步完成
- 编写Intercepror接口的实现类
- 设置插件的签名,告诉mybatis拦截哪个对象的哪个方法
- 最后将插件注册到全局配置文件中
/**
* @author chentengfei
* @desc Mybaits自定义插件: 注解声明mybatis当前插件拦截哪个对象的哪个方法
* type表示要拦截的目标对象 Executor.class StatementHandler.class ParameterHandler.class ResultSetHandler.class
* method表示要拦截的方法,
* args表示要拦截方法的参
* @date 2022-05-26 16:57:10
*/
//
@Intercepts({@Signature(type = StatementHandler.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})})
public class MybatisCustomerPlugin implements Interceptor {
/***
* 写一个插件,分三步完成
* 1.编写Intercepror接口的实现类
* 2.设置插件的签名,告诉mybatis拦截哪个对象的哪个方法
* 3.最后将插件注册到全局配置文件中
*/
//拦截目标对象的目标方法执行
@Override
public Object intercept(Invocation invocation) throws Throwable {
//被代理对象
Object target = invocation.getTarget();
//代理方法
Method method = invocation.getMethod();
//方法参数
Object[] args = invocation.getArgs();
//do someting...方法拦截前执行代码块
//插件的主要功能:在执行目标方法之前,可以对sql进行修改已完成特定的功能
//例如增加分页功能,实际就是给sql语句添加limit;还有其他等等操作都可以
//执行原来的方法
Object result = invocation.proceed();
//do someting...方法拦截后执行代码块
//返回执行后的返回值
return result;
}
//包装目标对象:为目标对象创建代理对象
@Override
public Object plugin(Object target) {
System.out.println("MybatisCustomerPlugin为目标对象"+target+"创建代理对象");
///this表示当前拦截器,target表示目标对象,wrap方法利用mybatis封装的方法为目标对象创建代理对象(没有拦截的对象会直接返回,不会创建代理对象)
Object wrap = Plugin.wrap(target, this);
return wrap;
}
//设置插件在配置文件中配置的参数值
@Override
public void setProperties(Properties properties) {
System.out.println(properties);
}
/***
* 在配置文件中写入plugins标签
* <plugins>
* <plugin interceptor="com.hxh.basic.project.plugins.MybatisCustomerPlugin">
* <property name="name" value="name"/>
* </plugin>
* </plugins>
*/
}
8. Mybaits延迟加载
- 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中,延迟加载修改了mappers配置文件中的多表查询语句,在本文中,将其拆分为两个单表查询。例如在查询账户及其用户信息时,由于懒加载策略,如果不需要,根据mappers的配置文件可知,会先对账户表进行查询并只显示账户表的内容,不会再进行用户信息的查询。如果必要,Mybatis才会进一步查询用户信息。这两步使用的都是单表查询的sql语句。因此延迟加载不仅简化了sql语句,分开的操作也节省cpu
- 不光是Mybatis,几乎所有包括Hibernate,支持延迟加载的原理都是一样的.
9. Mybatis一级缓存与二级缓存
一级缓存
- 基于PerpetualCache的HashMap本地缓存,
其存储作用域为Session
,当Session flush或者close之后,该Session中的所有Cache就将清空,默认打开一级缓存.- MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个Executor对象,Executor对象中持有一个PerpetualCache对象。当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
- 一级缓存时执行commit,close,增删改等操作,就会清空当前的一级缓存;当对SqlSession执行更新操作(update、delete、insert)后并执行commit时,不仅清空其自身的一级缓存(执行更新操作的效果),也清空二级缓存(执行commit()的效果)。
二级缓存
- 二级缓存与一级缓存其机制相同,默认也是采用PerpetualCache,HashMap存储,不同在于其
存储作用域为Mapper(Namespace)
,并且可以之定义存储源,如Ehcache.默认不打开二级缓存
,要开启二级缓存,使用二级缓存的属性类需要实现Serializable序列化接口
(可以用来保存对象的状态),可在它的映射配置文件中配置.- 它指的是Mybatis中
SqlSessionFactory对象的缓存
。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。- 类似于统计排行榜的查询,可能会涉及到多张表很多字段的查询统计排序,是非常费时费力的。如果每次都去数据库查询显示一次排行榜数据,那到排行榜这里,必定会卡顿很久,而且这种卡顿是用户不能忍受的。做成一级缓存也是不可行的,每次 SqlSession 请求,每个客户上来难道都要卡顿一次吗?所以,这种查询肯定要做成全局的缓存,当应用启动的时候就缓存这种查询数据,然后每一周刷新一次这种数据就可以了。
- 由此,简单总结二级缓存的特点和使用场景:
二级缓存作用于全局
,对于一些相当消耗性能的,并且对于时效性不敏感的查询可以使用二级缓存。注意,如果开启了二级缓存,查询的顺序是二级缓存 → 一级缓存 → 数据库
在 MyBatis 中使用二级缓存就必须要进行配置了,必须要有下面的步骤才能正常使用二级缓存:
1. 在全局设置中开启二级缓存
<settings>...
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
...
</settings>
在 xxxMapper.xml 中开启 标签
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024">
</cache>
可以简写为:
<cache/>
这样就表示在 xxxMapper.xml 中开启二级缓存了,因为 标签的每个属性都有默认值。cache 标签属性:
eviction
:缓存回收策略,这个属性又有下面几个值(默认是LRU
)
LRU
– 最近最少使用的。移除最长时间不被使用的对象。FIFO
– 先进先出。按对象进入缓存的顺序来移除它们。SOFT
– 软引用。移除基于垃圾回收器状态和软引用规则的对象。WEAK
– 弱引用。更积极地移除基于垃圾收集器状态和弱引用规则的对象。flushInterval
:刷新间隔,可以被设置为任意的正整数,而且它们代表一个合理的毫秒 形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。size
:引用数目,可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的 可用内存资源数目。默认值是 1024。readOnly
:只读属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓 存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。
10. Mybatis 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>
- 第二种是使用sql列的别名功能,将列的别名书写为对象属性名.
<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>
- 第三种:使用注解 select 的字段名保持与接口方法返回的 Java 类或集合的元素类的属性名称一致
@Select("select * from user")
List<User> selectAllUsers();
有了列名和属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回
,那些找不到映射关系的属性,是无法完成赋值的.根据解析得到 ResultMap 结合 sql 执行结果,通过反射创建对象,根据映射关系反射填充返回对象的属性,源码体现在DefaultResultSetHandler 的 handleResultSets 方法
,可参考:谈谈你对MyBatis结果集映射和参数绑定的理解
11.Mybatis动态sql
Mybatis动态sql可以在XML映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值完成逻辑判断,并动态拼接sql的功能.
Mybatis提供了如下动态slq
if、where、include标签
if标签
中可以判断传入的值是否符合某种规则,比如是否不为空;where标签
可以用来做动态拼接查询条件,当和if标签配合的时候,不用显示的声明类似where 1=1这种无用的条件,来达到匹配的时候and会多余的情况;include
可以把大量重复的代码整理起来,当使用的时候直接include即可,减少重复代码的编写
<!--动态Sql : where / if-->
<select id="findUserById" resultType="com.chentf.entity.User">
select <include refid="userInfo"/> from user
<where>
<if test="id != null and id != 0">
AND id = #{id}
</if>
<if test="name != null and name != ''">
AND name = #{name}
</if>
</where>
</select>
choose、when、otherwise 标签
- 类似于 Java 中的 switch、case、default。只有一个条件生效,也就是只执行满足的条件 when,没有满足的条件就执行 otherwise,表示默认条件
<!--动态Sql: choose、when、otherwise 标签-->
<select id="findUserById" resultType="com.chentf.entity.User">
select * from user
<where>
<choose>
<when test="name != null and name != ''">
AND name = #{name}
</when>
<otherwise>
AND id = #{id}
</otherwise>
</choose>
</where>
</select>
foreach 标签
- foreach标签可以把传入的集合对象进行遍历,然后把每一项的内容作为参数传到sql语句中,里面涉及到
item(具体的每一个对象)
,index(序号)
,open(开始符)
,close(结束符)
,separator(分隔符)
<!--动态Sql: foreach标签, 批量插入-->
<insert id="insertBatch" useGeneratedKeys="true" keyProperty="id">
insert into user (id, name)
values
<foreach collection="list" item="user" separator="," >
(#{user.id}, #{user.name})
</foreach>
</insert>
<!--动态Sql: foreach标签, in查询-->
<select id="dynamicSqlSelectList" resultType="com.chentf.entity.User">
SELECT * from user WHERE id in
<foreach collection="list" item="id" open="(" close=")" separator="," >
#{id}
</foreach>
</select>
map参数
- map标签需要结合MyBatis的参数注解 @Param()来使用,需要告诉Mybatis配置文件中的collection="map"里的map是一个参数
<!--动态Sql: foreach标签, map参数查询-->
<select id="findByMap" resultType="com.chentf.entity.User">
select * from user WHERE
<foreach collection="map" index="key" item="value" separator="=">
${key} = #{value}
</foreach>
</select>
set标签
- 适用于更新中,当匹配某个条件后,才会对该字段进行更新操作
<!--动态Sql: set 标签-->
<update id="updateSet" parameterType="com.chentf.entity.User">
update user
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
</set>
where id = #{id}
</update>
trim标签
- 是一个格式化标签,主要有4个参数:
- prefix(前缀)
- prefixOverrides(去掉第一个标记)
- suffix(后缀)
- suffixOverrides(去掉最后一个标记)
<!--动态Sql: trim 标签-->
<select id="findUser" resultType="com.chentf.entity.User">
select * from user
<trim prefix="where" suffix="order by id" prefixOverrides="and | or" suffixOverrides=",">
<if test="name != null and name != ''">
AND name = #{name}
</if>
<if test="id != null">
AND id = #{id}
</if>
</trim>
</select>
bind标签
- bind标签可以使用OGNL表达式创建一个变量并将其绑定到上下文中
- 在进行模糊查询时,如果使用“${}”拼接字符串,则无法防止 SQL 注入问题。如果使用字符串拼接函数或连接符号,但不同数据库的拼接函数或连接符号不同。
- 例如 MySQL 的 concat 函数、Oracle 的连接符号“||”,这样 SQL 映射文件就需要根据不同的数据库提供不同的实现,显然比较麻烦,且不利于代码的移植。幸运的是,MyBatis 提供了 bind 元素来解决这一问题。
<!--使用bind元素进行模糊查询-->
<select id="selectUserByBind" resultType="com.chentf.entity.MyUser" parameterType= "com.chentf.entity.MyUser">
<!-- bind 中的 uname 是 com.chentf.entity.MyUser 的属性名-->
<bind name="paran_uname" value="'%' + uname + '%'"/>
select * from user where uname like #{paran_uname}
</select>
public List<MyUser> selectUserByBind(MyUser user);
// 使用bind元素查询用户信息
MyUser bindmu=new MyUser();
bindmu.setUname ("张");
List<MyUser> listByBind=userDao.selectUserByBind(bindmu);
System.out.println ("bind 元素=========================");
for (MyUser myUser:listByBind) {
System.out.println(myUser);
}
动态sql的执行原理
- 首先在解析xml配置文件的时候,会有一个
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass)
的操作createSqlSource
底层使用了XMLScriptBuilder
来对xml中的标签进行解析XMLScriptBuilder
调用了parseScriptNode()
的方法,- 在
parseScriptNode()
的方法中有一个parseDynamicTags()
方法,会对nodeHandlers
里的标签根据不同的handler
来处理不同的标签
然后把DynamicContext结果放回SqlSource中
DynamicSqlSource获取BoundSql
- 在
Executor执行
的时候,调用DynamicSqlSource
的解析方法,并返回解析好的BoundSql
,和已经排好序,需要替换的参数
简单的说
:就是使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql
12. Mybatis的mapeper接口调用时有哪些要求
- Mapper接口方法名和mapper.xml中定义的每个sql的id相同.
- Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同.
- Mapper接口方法的输出参数类型和mapper.xml定义的每个sql的resultType的类型相同.
- Mapper.xml文件中的namespace即是mapper接口的类路径.
13. 模糊查询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>
14. 如何获取自动生成的(主)键值
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());
15. 在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, hashedpassword
from some_table
where username = #{username}
and hashedpassword = #{hashedpassword}
</select>
(3)第三种:多个参数封装成map
try{
//映射文件的命名空间.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();
}
16. Mybatis一对一,一对多的关联查询
<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>
<!--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>
17. Mybatis的SQL执行流程
1.MyBatis配置文件
- config.xml:配置了全局配置文件,配置了MyBatis的运行环境等信息。
- mapper,xml:sql的映射文件,配置了操作数据库的sql语句,此文件需在config.xml中加载。
2.SqlSessionFactory
- 通过MyBatis环境等配置信息构造SqlSessionFactory(会话工厂)。
3.SqlSession
- 通过会话工厂创建SqlSession(会话),对数据库进行增删改查操作。
4.Exector执行器
- MyBatis底层自定义了Exector执行器接口来具体操作数据库,Exector接口有两个实现,一个基本执行器(默认),一个是缓存执行器,SqlSession底层是通过Exector接口操作数据库。
5.MappedStatement
- MyBatis的一个底层封装对象,它包装了MyBatis配置信息与sql映射信息等。mapper.xml中的insert/select/update/delete标签对应一个MappedStatement对象。标签的id就是MappedStatement的id。
- MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo、Executor通过MappedStatement在执行sql前将输入的Java对象映射至sql中,输入参数映射就是JDBC编程对preparedStatement设置参数。
- MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过MappedStatement在执行sql后将输出结果映射至Java对象中,输出结果映射就是JDBC编程对结果的解析处理过程。