SSM框架(Spring,SpringMVC,MyBatis)

  • Post author:
  • Post category:其他


Spring

我们一般说的spring框架指的是spring fromwork,其目的是为了简化Java开发,核心容器有IOC(控制反转),AOP(面向切面编程),DI(依赖注入)

对于Spring IOC的认识

IOC(控制反转):反转的是对象的创建权,是将创建对象的权利由本来的new对象 交给了外部

让spring帮我们创建对象我们使用的时候直接去容器中获取即可

spring中有哪些IOC容器

IOC容器

作用

FileSystemXmlApplicationContext

程序初始化时导入bean 的配置文件,对bean进行加载

AnnotationConfigApplicationContext

基于Java配置文件配对bean进行加载

BeanFactory

通过类路径加载配置文件,BeanFactory创建完毕后 所有的bean都为延迟加载,当调用getBean()方法获取Bean对象的时候才创建Bean给我们.

ClassPathXmlApplicationContext

通过web.xml文件初始化bean的配置文件

BeanFactory和FactoryBean的区别

BeanFactory是Spring的容器,FactoryBean是一个特殊的bean,用于创建bean,创建出来的bean会放在容器中

对于AOP的理解

概念:面向切面编程.把公共的代码抽取出来,在不改变原始代码的基础上对方法进行功能增强.

连接点:所有可以被功能增强的方法就是连接点,下列代码中的save(),update(),select1()方法均属于连接点

@Repository
public class BookDaoImpl implements BookDao {

    public void save() {
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save ...");
    }

    public void update(){
        System.out.println("book dao update ...");
    }

    public long select1(){

        return 0;
    }
}

切入点:进行功能增强的方法就是切入点,在下列代码中方法selectAdvice就是切入点

//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {

    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    public void pt1(){};

    @Before("pt1()")
    public void method(){
        System.out.println("道前一叩三千年,回首凡尘不做仙");
    }

    @Pointcut("execution(long com.itheima.dao.BookDao.select1())")
    public void pt3(){}
    @Around("pt3()")
    public Object selectAdvice(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            pjp.proceed();
            System.out.println();
        }

        long end = System.currentTimeMillis();
        return end-start;

    }

}

通知:共性功能

通知类:通知所在的类就是通知类

切面:切入点+通知就是切面.描述的是在哪些方法上进行增强,下列代码就是配置切面,注解中的内容是指对哪些方法进行增强

@Pointcut("execution(void com.itheima.dao.BookDao.update())")
    public void pt1(){};

Spring AOP中的五种通知类型

通知类型

注解

作用

前置通知

@Before

在连接点方法前执行

后置通知

@After

在连接点方法后执行

环绕通知

@Around

在连接点的方法前后都执行

异常后通知

@AfterThrowing

在连接点的方法出现异常后执行

返回后通知

@AfterReturning

在连接点的方法正常返回,就会执行

Spring AOP和AspectJ AOP有什么区别?

Spring AOP:

1. 基于动态来实现,默认如果使用接口的,用JDK提供的动态代理实现,如果是方法,则使用CGLib实现
2. Spring AOP 需要依赖IOC容器来管理,并且只作用于Spring容器,使用纯Java代码实现
3. 在性能上由于Spring是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法带哦用上也会增加栈的深度,使得Spring AOP的性能不如AspectJ好
4. Spring AOP 致力于解决企业级开发中最普遍的AOP(方法织入)

AspectJ AOP

AspectJ 是一个医用的功能强大的AOP框架,属于编译时增强,可以单独使用,也可以整合到其他的框架中,是AOP编程的完全解决方案,AspectJ 需要使用到单独的编译器ajc
1. 编译器织入:如果类A使用了AspectJ添加了一个属性,类B引用了它,这个场景就需要编译器的时候进行织入,否则无法编译类B
2. 编译后织入:也就是已经生成了.class文件,或者已经打jar包了,这种情况我们需要增强处理的话,就是要用到编译后织入
3. 类加载后织入:指的是在加载类的时候进行织入

AOP

区别

Spring AOP

  1. 在纯java中实现

  1. 编译器javac

  1. 只在运行时织入

  1. 仅支持方法级编织

  1. 只可在Spring管理的Bean上实现

  1. 比AspectJ慢很多

  1. 易于使用

  1. 创建目标对象的代理,切面在代理中执行

AspectJ AOP

  1. 用Java编程语言扩展实现

  1. 一般需要ajc编译

  1. 支持编译时,编译后,加载时织入

  1. 可编织字段,方法,构造函数,静态初始值等

  1. 可在所有域对象实现

  1. 支持所有切入点

  1. 速度更快

  1. 比AOP复杂

  1. 执行程序前,各方面直接织入代码中

Spring中的单例bean是线程安全问题的吗

不安全,但是Spring中dean是无状态的,也就是不存储数据,所以在某种意义上是线程安全的

如何解决该问题:

1.把bean的作用域改为非单例的prototype

2.把共享变量放到ThreadLocal中,ThreadLocal是线程私有变量,线程间隔离,也可以解决线程安全问题

Spring中的bean生命周期?

BeanDefinition -> 实例化 -> 依赖注入 -> 初始化 -> 放到容器中等待使用 -> 销毁

  1. 先通过配置文件或者注解拿到所有的BeanDefinition,并放到BeanDefinitionMap中

  1. 从BeanDefinitionMap中拿到Bean定义并进行实例化 也就是 new 对象

  1. 依赖注入,给容器中bean中的属性赋值

  1. 初始化,给属性赋值

  1. 初始化完成后,bean真正创建完成,就可以把bean放到spring容器中,ConcurrentHashMap,我们使用的时候就可以通过getBean来获取bean

  1. 容器销毁时,bean也随之销毁

BeanDefinition:是bean定义.描述的是这个bean的类型,即这个bean 的名字,有哪些属性,有哪些构造函数,有哪些方法.

Spring 框架中都用到了哪些设计模式?

工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例

单例模式:Bean默认为单例模式,scop=”singleton”,注册式单例魔兽,bean存放于Map中.bean name作为key,bean作为value

原型模式:在spring中用到的原型模式有:scope=”prototype”,每次获取的都是通过克隆生成的新实例,对其修改时对原实例对象不造成影响.

代理模式:Spring的AOP功能呢个用到了JDK的动态代理和CGLIB字节码生成技术

模板方法:用来解决代码重复的问题,比如.RestTemplate,JmsTemplate,JapTemplate.

观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变是,所有依赖于它的对象都会得到通知被主动更新,如Spring中listener的实现 ApplicationListener

你用到Spring中的哪些注解 ?

定义bean:@Component,@Controller,@Service,@Repository

依赖注入:@Autowired,@Qualifier,@Resource,@Value

定义第三方bean:@Bean

配置类注解:@Configuration,@Import,@PropertySource,@ComponentScan

bean的作用范围和生命周期:@Scop,@PostConstruct(bean初始化时需要执行的方法上加次注解),@PreDestroy(Bean销毁时需要执行的方法上加此注解)

Spring 事务

事务作用:在数据层保障一系列的数据库操作同时成功,同时失败

事务的特性:

  1. 原子性:事务的所有操作,要么全部完成,要么全部失败,不会在中间某个环节结束,事务在执行过程中发生错误,会被回滚到事务开启之前的状态

  1. 一致性:在事务开始之前和事务结束之后,数据库的完成性没有被破坏

  1. 事务隔离:数据库允许多个并发事务同时对数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行导致的数据不一致

  1. 持久性:事务处理结束后,对数据库的修改就是永久性的,即使系统故障也不会丢失

事务的4中隔离级别:

  1. 未提交读:最低的隔离级别,可能出现”脏读”,即事务可以读取到其他事物未提交的修改,如果此时另一个事务回滚,那么当前事务读取到的就是脏数据

  1. 提交读:指一个事务会遇到不可重复度的问题,不可重复读是指,在一个事务中多次读取到同一个数据,这个事务没有结束的时候,如果另一个事务修改了此数据,就会导致两次读取的数据不一致

  1. 可重复读:一个事务可能会遇到幻读的问题,指在一个事务中,前后两次查询同一个范围的数据,后一次看到了前一次没有看到的数据

  1. 串行化:事务中最严格的隔离级别,所有的事务按照严格的次序依次执行,因此脏读,不可重复度,幻读都不会出现,虽然在串行化的隔离级别下的事务具有最高的安全性,但是相应的效率会大大降低,应用程序的性能也会急剧降低.

spring事务管理的作用

在数据层或者业务层保障一系列的数据库操作同时成功,同时失败

Spring事务管理的方式有几种?

spring的事务管理提供了两种方式:编程式事务和声明式事务

编程式事务

编程式事务是指将事务管理代码嵌入到业务代码中,用来控制事务的提交和回滚

spring中可以使用TransactionTemplate,PlatformTransactionManager来实现编程式事务

 @Test
 public void testDelete(){
//删除id为1的用户
   accountService.delete(1);
 }

private TransactionTemplate transactionTemplate;
public void delete1(final Integer id) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus){
                try {
                    accountDao.delete(id);
                    //手动制造异常
                    int a = 1 / 0;
                } catch (Exception e) {
                    //事务回滚
                    e.printStackTrace();
                }
            }
        });
}

private PlatformTransactionManager transactionManager;
public void delete2(Integer id) {
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            accountDao.delete(id);
            //手动制造异常
            int a = 1 / 0;
        } catch (Exception e) {
            //事务回滚
            transactionManager.rollback(status);
        }
    }

在上述代码中testDelete为测试代码 方法delete1()使用了TransactionTemplate来实现.方法delete2()使用了PlatformTransactionManager,当代码执行到 int a = 1/0; 抛出异常后 id=1的用户并没有被删除,因为当程序出现异常后两段代码都对事务进行了回滚.使数据回到了操作之前的状态.

声明式事务

声明式事务是将事务管理的代码从业务方法中抽离出来,以声明的方式来实现事务管理,对于开发者来说,声明式事务显然比编程式事务更好用 ,更便捷,这里就需要用到Spring中的AOP,本质是在方法开始之前 创建或者加入一个事务,执行完毕后根据情况提交或者回滚.


声明式事务虽然优于编程式事务但是声明式事务的管理力度是方法级别的,也就是注解或者配置只能加在方法上,而编程式事务可以到代码块级别

使用注解实现spring事务需要 此处连接数据库使用的是mybatis

1.使用@Transactional注解配置当前的接口方法具有事务

public interface AccountService {
    //配置当前接口方法具有事务
    @Transactional
    public void transfer(String out,String in ,Double money) ;
}
// 注解识时务可以添加到业务方法上,表示当前的方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务

2.设置事务管理器(将事务管理器添加到IOC容器中)

//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
    DataSourceTransactionManager dtm = new DataSourceTransactionManager();
    transactionManager.setDataSource(dataSource);
    return transactionManager;
}

3.使用@EnableTransactionManagement开启注解驱动

@Configuration
@ComponentScan("com.springdemo")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

需注意如果在使用@Transactional注解时,某个方法是private的,那么@Transactional将会失效,因为底层cglib是基于继承来实现的,子类无法重载父类的私有方法,所以导致@Transactional失效.

Spring的事务传播行为

PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,则加入该事务,该设置是最常用的设置

PROPAGATION_SUPPORTS(默认):支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行

PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常.

PROPAGATION_REQUIRES_NEW:创建新的事务,无论当前存在不存在事务,都创建新事务

PROPAGATION_NOT_SUPPORTED:以非事务的方式执行操作,如果当前存在事务,就把当前事务挂起

PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常

PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行,如果没有事务,按REQUIRED执行

Spring中的事务是如何实现的 ?

  1. Spring事务底层是基于数据库事务和AOP机制实现的

  1. 首先对于使用了@Transactional注解的Bean,Spring会创建一个代理对象作为Bean

  1. 当调用代理对象的方法时,会先判断该方法上是否加了@Transactional注解

  1. 如果加了@Transactional注解那么利用事务管理器创建一个数据库连接

  1. 并且修改数据库连接的autocommit属性为false,这个属性的作用是禁止此连接的自动提交,这是Spring实现事务非常重要的异步

  1. 执行当前方法,方法中会执行sql

  1. 执行完当前方法后,如果没有出现异常就会直接提交事务

  1. 如果出现异常,并且这个异常是需要事务回滚的,就会进行事务回滚,否则依旧提交事务

  1. Spring事务的隔离级别对应的就是数据库的隔离级别

  1. Spring事务的传播机制是Spring事务自己实现的

  1. Spring事务的传播机制是基于数据库连接来做的,一个数据库连接一个事务

如果传播机制配置需要新开一个事务,那么实际上就是先建立一个数据库连接,在此新数据库连接上执行sql

使用@Autowired注解自动装配的过程是怎样的?
  1. 在启动Spring IOC时,容器会自动装配一个后置处理器(AutowiredAnnotationBeanPostProcesspr),当容器扫描到@Autowired时,就会在IOC容器中去自动查找所需要的Bean对象

  1. 在使用@Autowired时,容器首先会去查找对应类型的Bean对象,当查询结果是一个的时候,就会自动进行装配,如果查询结果不止一个,那么@Autowired会根据名称来查找.如果上述的查找结果为空,那么就会抛出异常.可以使用@Autowired中的required=false

spring 自动装配bean有哪些方式?

默认有五中装配方式

  1. no:默认的方式是不进行装配,通过收工设置ref属性来进行装配bean

  1. byName:通过Bean的名字进行装配,如果一个bean的proterty与另一个bean的name相同,进行自动装配

  1. byType:通过类型进行装配

  1. constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配

  1. autodetect: 自动探测,如果有构造方法,就根据构造方法自动转配,否则使用byType的方式进行转配

Spring支持的几种bean的作用域 Scope ?

Spring框架支持五中bean的作用域

  1. singleton:(默认)单例bean,即每个bean只有一个实例

  1. prototypr:非单例,每一次请求都会产生一个新的bean实例

  1. request:针对每一次的HTTP请求都会产生一个新的bean

  1. session:与request范围类似,确保每个session域中有一个bean实例,单个bean可有多个session

  1. global-session:全局作用域,所有的会话共享同一个bean实例

request,session,global-session三种作用域仅适用于web的SpringApplicationContext环境使用

Spring的优点

  1. spring属于低侵入式设计,代码的污染极低

  1. spring的DI机制将对象之间的依赖关系交由框架处理,降低了组件的耦合性

  1. spring提供了AOP技术,支持将一些通用任务,如安全,事务,日志, 权限等进行集中处式管理,从而提供更好的复用

Spriong有哪些模块组成

  1. Spring Core:核心类库,提供IOC服务

  1. Spring Context:提供框架式的Bean访问方式,以及企业级功能

  1. Spring AOP:AOP服务

  1. Spring Dao:对JDBC的抽象,简化了数据访问异常的处理

  1. Spring ORM:对现有ORM框架的支持

  1. Spring Web:提供了基本的面向web的重合特性

  1. Spring MVC:提供面向Web应用的Mode-View-Controller实现

Spring 容器的启动流程

创建Spring容器,也就是启动Spring时首先会进行扫描,扫描得到所有的BeanDefinition对象,并存在一个map中,然后筛选出非懒加载的BeanDefinition进行创回见Bean,利用BeanDifinition创建Bean就是创建Bean的生命周期,这期间包括合并BeanDifinition,推断构造方法,实例化,属性填充,初始化前,初始化,初始化后等步骤.

其中AOP就是发生在初始化后这一步骤中,单例Bean创建完成之后,Spring会发布一个容器的启东事件,之后Spring启动结束.

Spring如何处理线程并发问题

一般情况况下只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分的Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用了ThreadLocal进行处理,解决线程安全问题

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题. 同步机制采用了”时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没有得到锁的线程则需要排队,ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突.

什么是Spring Refresh?

Spring容器在创建之后,会调用它的refresh方法刷新Spring应用的上下文.

Spring核心类有哪些,各有什么用

  1. BeanFactory:产生一个新的实例,可以实现单例模式

  1. BeanWrapper:提供统一的get以及set方法

  1. ApplicationContext:提供框架的实现,包括BeanFactory所有功能

如何解决POST请求/GET请求中乱码问题

  1. 解决POST请求乱码问题:在web.xml配置一个CharacterE你从定Filtre过滤器设置为utf-8

  1. get请求中文参数出现乱码问题解决方法有两个

  1. 修改tomcat配置文件添加编码与工程编码一致

  1. 对参数进行重写编码

Spring MVC

Spring MVC 是一个基于java的实现了mvc设计模式的请求驱动类型的轻量级web框架,通过吧Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用用逻辑清晰的几部分,简化了开发,减少出错,方便组内开发人员之间的配合.

Spring MVC的优点

  1. 可以支持各种视图技术,而不是仅仅局限于JSP

  1. 与Spring框架继承

  1. 清晰的角色分配:前端控制器,请求处理器映射,处理器适配器,视图解析器

  1. 支持各种请求资源的映射策略

Spring MVC 的工作流程

  1. 前端发起请求到DispatchServlet

  1. DispatchServlet访问 处理器映射器 把前端请求路径 转换成后端的 Handler并返回

  1. DispatchServlet 访问处理器适配器找到合适的Handler(Controller)处理逻辑并返回ModelAndView

  1. DispatchServlet访问视图解析器 解析 ModelAndView并返回给前端View对象

  1. DispatchServlet把view对象渲染成jsp页面并返回给前端

Spring MVC 常用注解

@RestController

@Controller和@ResponseBody的组合注解 标注当前类为控制器类表示方法的返回值直接以指定的格式写入Http response body中,而不是解析为跳转路径。

@RequestMapping

接口请映射

@GetMapping

GET请求方式映射

@PutMapping

PUT请求方式映射

@PostMapping

POST请求方式映射

@DeleteMapping

DELETE请求方式映射

@RequestBody

接受请求体数据,JSON会自动转对象

@ResponseBody

响应数据自动换化为JSON

@RequestParam

接受请求参数

@Cookie

接受请求中的cookie

@RequestHeader

接受接受请求头

@PathVariable

声明路径变量

在浏览器输入地址到服务器响应数据的过程

  1. 在浏览器输入url,直接输入ip或者输入域名

  1. 如果输入的是域名就需要通过DNS解析将域名解析成IP地址,通过IP来确认访问的是哪个服务器

  1. 建立TCP请求(即三次握手)

  1. 发送http请求

  1. 服务器请求,并将结果返回给浏览器

  1. 最后断开TCP连接(即四次挥手)

  1. 浏览器根据返回结果进行处理以及页面的渲染

Spring里面的连接器是怎么写的?

一种是实现HandlerInterceptor接口或者继承HandlerInterceptor接口的实现类来定义,HandlerInterceptor接口中的方法都是default的,表示不强制重写方法,但是我们一般会重写其中的 preHandler,postHandler,afterCompletion这三个方法.

public interface HandlerInterceptor {

    // 在Controller得到请求的时候进行拦截,如果不放行请求则Controller得不到请求
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
    
    //在Controller处理完请求要进行视图的跳转的时候进行调用此方法
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    // 在视图跳转完成后调用此方法
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

一种是继承适配器类 WebRequestInterceptor或实现WebRequestInterceptor 接口的实现类来定义。

public interface WebRequestInterceptor {

    /** Controller得到请求之前调用此方法拦截请求,
        但是不会像HandlerInterceptor一样会拦截请求而不释放。
    **/
    void preHandle(WebRequest request) throws Exception;

    // Controller处理完请求后调用此方法
    void postHandle(WebRequest request, @Nullable ModelMap model) throws Exception;

    // 视图跳转后调用此方法。
    void afterCompletion(WebRequest request, @Nullable Exception ex) throws Exception;
}

SpringMVC怎么和AJAX互相调用?

通过Jackson框架就可以吧Java中的对象转化为Js可以识别的JSON对象

1.需要加入Jackson.jar包 或者导入jackson的依赖

2.在配置文件中配置json的映射

3.在接受ajax方法里面可以直接返回Object,List等,要在方法前面加上@ResponseBody

SpringMVC的异常处理

当我们进行了异常处理之后,再出现异常的时候前端就不会直接出现 500 的错误之类的,而是直接跳转到某个页面或者给出对应的提示.

常见的两种异常处理的方式:

  1. 使用SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver

<!-- 配置异常处理器 -->
<bean class = "org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="defaultErrorView" value = "error"/>
</bean>

上面使用的默认的异常页面,当出现我们没有指定的异常的话,他就会跳转到默认的异常页面

  1. 实现Spring的异常处理接口HandlerExceptionResolver自定义自己的异常处理器

@Component
public class GlobalException implements HandlerExceptionResolver {
    /**
     *
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o 异常处理的目标
     * @param e 异常处理的类型
     * @return
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        ModelAndView mv=new ModelAndView();
        mv.setViewName("error");
        //判断异常的分类
        if(e instanceof RuntimeException){
                RuntimeException ex=(RuntimeException)e;
                System.out.println(ex.getMessage());
                mv.addObject("msg",ex.getMessage());
        }
        return mv;
    }
}

创建异常类

public class BusinessException extends RuntimeException {
   
    public BusinessException() {
    }
 
    public BusinessException(String message) {
        super(message);
    }
 
    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }
 
    public BusinessException(Throwable cause) {
        super(cause);
    }  
}

表现层代码

@RequestMapping("/querySingleBook")
    @ResponseBody
    public Book querySingleBook(Integer bookId){
        Book book = bookService.selectByPrimaryKey(bookId);
        if(bookId>100)
            throw new BusinessException("书本编号小于100,异常抛出!!!");
        book.setDate(new Date());
        return book;
    }
  1. 使用@ControllerAdvice+@ExceptionHandler实现全局异常处理

  1. 创建全局异常处理器

  1. 自定义了返回给前端的对象R

@Data
public class R<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    private Integer code; //编码:1成功,0和其它数字为失败

    private String msg; //错误信息

    private T data; //数据

    private Map map = new HashMap(); //动态数据
 
/**
 * SpingMVC提供的第三种种全局异常处理方式
 * 1)@ControllerAdvice +@ExceptionHandler
 * 2)@RestControllerAdvice +@ExceptionHandler
 *      @RestControllerAdvice ==@Controller +@ResponseBody 返回JSON的数据格式,绕开视图解析器
 */
@RestControllerAdvice(annotations = {RestController.class, Controller.class})
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex) {
        if (ex.getMessage().contains("Duplicate entry")) {
            String[] strings = ex.getMessage().split(" ");
            String s = strings[2] + "已存在";
            return R.error(s);
        }
        return R.error("未知错误");
    }
}

SpringMVC的控制器是不是单例模式,如果是,有什么问题,怎么解决?


单例模式

:一个对象多次使用,并且只有方法没有属性,或者有属性,属性不变,就可以通过单例模式私有化 对象的构造方法,然后通过get对象的方法获取对象.这样操作就节省了创建,删除对象的性能问题.


问题:在多线程访问的时候会有线程安全问题


解决方案:

在控制器里不写可变状态量,如果需要使用这些可变状态,可以使用ThreadLocal机制解决,为每个线程单独生成一份变量副本独立操作,互不影响.

SpringMVC中函数的返回值是什么?

ModelAndView可以返回数据和视图

String

Mybatis/Mybatis-plus

Mybatis的工作原理?

  1. 加载MyBatis配置文件,myubatis-config.xml为Mybatis的全局配置文件,用于配置数据库连接信息

  1. 加载映射文件Mapper.xml,也就是SQL映射文件,该文件配置了操作数据库的SQL语句,需要在Mybatis配置文件mybatis-config.xml中加载.mybatis-config.xml文件中可以加载多个映射文件,每个映射文件对应数据库中的一张表.

  1. 根据MyBatis环境配置信息构造会话工厂SqlSessionFactory

  1. 创建会话对象SqlSession,这个对象中包含了执行SQL语句的所有方法

SqlSessionFactory sqlSessionFactory = new SqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        xxxMapper mapper = sqlSession.getMapper(xxxMapper.class);
        Student student = mapper.selectByIds(1);
  1. 通过Executor执行器动态的生成需要执行的SQL语句

<select id="selectById" parameterType="Long" resultMap="TbBusinessResult">
       select * from student where id = #{id};
    </select>
  1. MapedStatement对象存储要映射的SQL语句的id,参数等信息

  1. 执行输入参数映射

  1. 根据查询结果映射封装数据

在mapper中如何传递多个参数?

  1. 顺序传参法

  1. 使用@Param注解传参

  1. 封装到对象或者Map集合中

mybatis映射文件汇总用到哪些标签?

<select> 查询标签

<select id="selectById" parameterType="Long" resultMap="TbResult">
        select * from person where id = #{id}
    </select>

<update> 更新标签

<update id="updateTbBusiness" parameterType="TbBusiness">
        update tb_business phone='123456678911'  where id = #{id}
</update>

<delete> 删除标签

<insert> 添加标签

<where> 类似于SQL语句汇总的where

<if> 动态SQL 对条件进行判断

<freach> 循环遍历,凭借SQL,一般用于批处理中

<set> 类似于SQL语句中的set关键字

<resultMap> 结果集映射标签

<resultMap type="Person" id="TbBusinessResult">
        <result property="id"    column="id"    />
        <result property="name"    column="name"    />
        <result property="phone"    column="phone"    />
</resultMap>

mybatis如何实现多表查询

编写多表查询语句,使用ResultMap建立结果集映射

使用延迟加载,多个SQL语句组合

<settings>
    <!-- 延迟加载总开关 -->
    <setting name="lazyLoadingEnabled" value="ture"/>  <!-- 默认是false -->
</settings>

一对一的情况下

public class Teacher {
    private int tid;
    private String tname;
    private String tsex;
    private Date tbirthday;
    private String taddress;
    private String temail;
    private String tmoney;
    private int cid;
    //外部属性 自定义的对象
    private Course course;
}

Mapper.xml
<mapper namespace="com.day4.mapper.TeacherMapper">
    <!-- 类路径 -->
    <resultMap id="Teahcer_Scoure_Map" type="Teacher">
        <result property="tid" column="tid"/>
        <result property="tname" column="tname"/>
        <result property="tbirthday" column="tbirthday"/>
        <result property="tsex" column="tsex"/>
        <result property="taddress" column="taddress"/>
        <result property="temail" column="temail"/>
        <result property="tmoney" column="tmoney"/>
        <result property="cid" column="cid"/>
        
        <!-- 使用assocation把标签指定外部属性 -->
        <association property="course"  >
            <result column="cid" property="cid"/>
            <result column="cname" property="cname"/>
        </association>
    </resultMap>



    <select id="FindAllTeacherbysqlmapper" resultMap="Teahcer_Scoure_Map">
        select * from course inner join teacher
        on teacher.cid = course.cid

    </select>

</mapper>

一对多的情况

public class Course {
    private int cid;
    private String cname;
    //外部属性
    private List<Teacher> tlsit;
   }

mapper.xml

<mapper namespace="com.day4.mapper.CourseMapper">
    <!-- 类路径 -->
    <resultMap id="Teahcer_Scoure_Map" type="course">

        <result column="cid" property="cid"/>
        <result column="cname" property="cname"/>
        
        <!-- 使用collection标签将对象中的属性与数据库字段名映射到一起  -->
        <collection property="tlsit" ofType="Teacher" >

            <result property="tid" column="tid"/>
            <result property="tname" column="tname"/>
            <result property="tbirthday" column="tbirthday"/>
            <result property="tsex" column="tsex"/>
            <result property="taddress" column="taddress"/>
            <result property="temail" column="temail"/>
            <result property="tmoney" column="tmoney"/>

        </collection>
    </resultMap>
    <select id="FindAllCoursebysqlmapper" resultMap="Teahcer_Scoure_Map">
        select * from teacher inner join course
        on teacher.cid = course.cid
    </select>

</mapper>

Mapper文件中的foreach标签有哪些属性

collection:指定遍历的集合,如果遍历的是数组就是array,遍历的是list集合就是list,遍历的是对象或者Map的集合属性就是对象属性名称或者map的key

item:遍历每一项

index:遍历过程中的索引

open:字符串拼接的前缀

separator:分隔符

close:字符串拼接后

Mybaits如何获取生成的主键

  1. insert标签上使用useGeneratedKeys-“ture” keyProperty指定主键名称 这里对应的是实体类的属性,而不是数据库的字段

<insert useGeneratedKeys="true" id="insertById" keyProperty="id" >

        insert into user(user_name) values(#{userName});
    </insert>
  1. 在insert标签中使用selectKey标签配置keyproperties属性 keyColum通过select last_insert_id()查询

<mapper namespace="com.example.wjtweb.mapper.SelectKeyMapper">
   
    <insert id="insert" parameterType="com.example.wjtweb.pojo.Goods">
    
        <selectKey keyProperty="id" order="AFTER" resultType="Integer">
            SELECT LAST_INSERT_ID()
        </selectKey>
        INSERT INTO Goods (MID,GNAME,PRICE,AMOUNT,imageName)
        VALUES (#{mid},#{gname},#{price},#{amount},#{imageName});
    </insert>
 
</mapper>

selectKey 会将 SELECT LASTINSERTID() 的结果放入到传入的model的主键里面,keyProperty 对应的model中的主键的属性名,这里是 Goods 中的id,因为它跟数据库的主键对应,order AFTER 表示 SELECT LASTINSERTID() 在

insert

执行之后执行,多用于自增主键,BEFORE表示SELECT LASTINSERTID() 在insert执行之前执行,这样的话就拿不到主键了,这种适合那种主键不是自增的类型 resultType 主键类型

当实体类中的属性名和表中的字段名不一致,怎么办?

  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>
  1. 通过resultMap来映射字段名和实体类属性名的一一对应关系


<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>

在使用MyBatis的mapper接口调用时有哪些要求?

  1. Mapper接口方法名和mapper.xml中的定义的每个sql的id相同

  1. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同.

  1. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

  1. Mapper.xml文件中的namespace就是mapper接口的类路径

Dao接口中的方法,参数不同时,方法能重载吗?

不可以重载,Dao接口就是Mapper接口,接口的全限定名,就是映射文件中的namespase的值,接口的方法名就是映射文件中Mapper的Statement的id值,接口方法内的参数,就是传递给sql的参数.而且因为Mapper接口没有实现类,当调用方法的时候,接口全限定名+方法名拼接字符串作为key的值,可唯一定位一个MapperStatement.在mybatis中,每一个sql标签,都会被解析为一个MapperStatement对象.

例如 namespace=”com.huike.review.mapper.MybatisReviewMapper” 下面id为 insertById的MapperStatement.

Mapper 接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK动态代理为 Mapper 接口生成代理对象 proxy,代理对象会拦截接口方法,转而执行 MapperStatement 所代表的 sql,然后将 sql 执行结果返回。

<mapper namespace="com.huike.review.mapper.MybatisReviewMapper">

    <insert useGeneratedKeys="true" id="insertById" keyProperty="id" >

        insert into user(user_name) values(#{userName});
    </insert>
</mapper>

Mybatis是如何进行分页的?分页插件的原理是什么?

Mybatis使用RowBounds对象进行分页,他是针对ResultSet结果集进行的内存分页,而非物理分页,可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页.

分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件 的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数.

Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?

mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的是一对多查询.

可以配置lazyLoadingEnable=ture|false来完成,原理是使用了CGLIB创建目标对象的代理对象.

Mybatis的一级,二级缓存?

一级缓存:基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,当Session flush或者close之后,该Session中所有Cache就将清空,默认打开一级缓存

二级缓存与一级缓存机制相同,默认也是采用HashMap存储,不同在于其作用域为Mapper(Namespace),并且可以自定义存储资源,如Ehcache.默认是不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件配置

使用Mybatis-Plus的过程的多表查询如何做?

使用mybatis的映射配置文件 xxxMapper.xml 自己编写Sql语句 ,编写ResultMap映射

<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>

mybatis是如何实现实体类和数据库表的映射的?

  1. 通过xml映射文件中的resultMap

<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>
  1. 通过注解@Results和@Result

@Select("select * from t_user where user_name = #{userName}")
@Results(
        @Result(property = "userId", column = "user_id"),
        @Result(property = "userName", column = "user_name")
)
User getUserByName(@Param("userName") String userName);
  1. 通过在配置文件中属性配置完成映射

 mybatis:
  type-aliases-package: cn.itcast.order.pojo
  configuration:
    #下划线可以映射驼峰
    map-underscore-to-camel-case: true

4.通过sql语句中定义别名完成映射

通过注解:


  • @TableName

    :数据库表相关


  • @TableId

    :表主键标识


  • @TableField

    :表字段标识


  • @TableLogic

    :表字段逻辑处理注解(逻辑删除)


@TableField(exist = false)

:表示该属性不为数据库表字段,但又是必须使用的。


@TableField(exist = true)

:表示该属性为数据库表字段。

MyBatis-Plus自动填充

通过@TableFile中的file属性可以指定字段什么时候被自动填充还可以实现自定义实现MyMetaObjectHandler

#{}和${}的区别

#{}是占位符,预编译处理,${}是拼接符,字符串替换,没有预编译处理.

Mybatis在处理#{}时,#{}传入参数是以字符串传入的,会将sql中的#{}替换为?号,调用PreparedStatement的set方法赋值

#{}可以有效的防止sql注入问题的出现,提高系统的安全性,而${}不能防止SQL注入问题的出现

#{}的变量替换是在数据库中,${}的变量替换是在数据库系统外

Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理吗?

mybatis的动态sql可以让我们在xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态凭借sql的功能.

动态sql标签:

trim:类似于元素替换的意思,可以将and替换成where

<select id="getUser4" resultMap="u">
    SELECT * FROM user2
    <trim prefix="where" prefixOverrides="and">
        AND id = 1
    </trim>
</select>

| where:当我们在编写XML中的sql语句的时候,在添加查询条件的时候,在查询条件之前添加了where id = 1 ,然后再在后面追加and的时候每次怎么写有点麻烦这时候我们就可以通过where标签

<select id="getUser3" resultMap="u">
    SELECT * FROM user2
    <where>
        <choose>
            <when test="id!=null">
                AND id = #{id}
            </when>
            <when test="address!=null">
                AND address = #{address}
            </when>
            <when test="username!=null">
                AND user_name LIKE concat(#{username},'%')
            </when>
         
        </choose>
    </where>
</select>

| set:我们在更新标的时候使用的元素,通过set标签我们可以逐个字段的修改一条数据

<update id="update">
    UPDATE user2
    <set>
        <if test="username!=null">
            user_name = #{username},
        </if>
        <if test="password!=null">
            password = #{password}
        </if>
    </set>
    WHERE id = #{id}
</update>

|

foreach

标签用来遍历集合.foreach标签的属性主要有item,index,collection,open,separator,close

collection:传入参数集合的名称,List对象默认用list代替作为键;数组对象有array代替作为键;map对象没有默认的键,该属性必须指定

item:集合中元素迭代时的我别名,该参数必选

index:在list和数据中index是元素的序号;在map中index是元素的key,该参数可选

open:开始符号,一般和close=’)’合用,该参数可选

separator:元素之间的分隔符号,separator=”,”,会自动的在元素中间隔开.避免手动输入逗号导致sql错误,该参数可选

close:关闭符号,参数可选

| if标签是mybatis动态sql 的判断标签,类似Java中的if语句,if标签中的test属性必选,在test属性中写入条件

<select id="getUser" resultMap="u" parameterType="String">
    select * from user2
    <if test="address!=null and address !=''">
        WHERE address LIKE concat('%',#{address},'%')
    </if>
</select>

| choose标签类似于java中的switch,常常配合when和otherwise使用, otherwise标签中的语句 表示默认的条件,当其他条件都不成立时执行otherwise中的条件

<select id="getUser2" resultMap="u">
    SELECT * FROM user2 WHERE 1=1
    <choose>
        <when test="id!=null">
            AND id = #{id}
        </when>
        <when test="address!=null">
            AND address = #{address}
        </when>
        <when test="username!=null">
            AND user_name LIKE concat(#{username},'%')
        </when>
        <otherwise>
            AND 10 > id
        </otherwise>
    </choose>
</select>

| when 标签相当于Java中switch语句中的case

bind标签可以预先定义一些变量,然后在查询语句中使用

<select id="getUserByName" resultMap="u">
    <bind name="un" value="username+'%'"></bind>
    SELECT* FROM user2 WHERE user_name LIKE #{un}
</select>

其执行原理,使用OGNL从sql参数对象中计算表达式的值,更具表达式的值动态的拼接sql,一次来完成动态sql 的功能



版权声明:本文为c1210283911原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。