@MRsunhuimin
2022-07-11T13:25:39.000000Z
字数 25155
阅读 500
面试问题总结
我在中国太平保险公司做了太平个险项目,太平银险项目以及太平服拓项目,客户是太平集团的代理人和集团总部以及各分支机构的管理人员,项目内容包括了人员模块,组织模块,佣金模块,考核模块,押金模块和集中支付模块等,数据库是Oracle,前台使用的是Vue和ElementUi框架,后台用的SpringCloud微服务框架,以及使用了redis做分布式锁,使用RocketMq消息队列,我在项目中主要负责押金模块和部分集中支付模块,押金模块主要做的是代理人缴纳押金上岗,还欠款和离职退押金;集中支付模块主要是让代理人的佣金统一由总公司进行集中发放,通过与资金系统交互进行,总公司财务部在资金系统审核后,与银行进行后续支付处理的发放流程。
https://blog.csdn.net/weixin_41217541/article/details/104718834?
微服务五大组件
Eureka:个服务启动时,Eureka会将服务注册到EurekaService,并且EurakeClient还可以返回过来从EurekaService拉去注册表,从而知道服务在哪里
Ribbon:服务间发起请求的时候,基于Ribbon服务做到负载均衡,从一个服务的对台机器中选择一台
Feign:基于fegin的动态代理机制,根据注解和选择机器,拼接Url地址,发起请求
Hystrix:发起的请求是通过Hystrix的线程池来走,不同的服走不同的线程池,实现了不同的服务调度隔离,避免服务雪崩的问题
Zuul:如果前端后端移动端调用后台系统,统一走zull网关进入,有zull网关转发请求给对应的服务
专门负责服务的注册和发现
Eureka service:注册中心,里面有一个注册表,保存了各个服务所在的机器和端口号
Eurake Client:负责将这个服务的信息注册到Eureka Server中
两者区别:https://www.cnblogs.com/lgg20/p/12507845.html
微服务网关,这个组件是负责网络路由的
Feign怎么知道请求那台服务器呢,这是SpringCloud就派上用场了,Ribbon 务就是解决这个问题的,他的作用是负载均衡,会帮你在每一次请求的时候选择一台机器,均匀的把请求发送到各个机器上 ,Ribbon的负载均衡默认的使用RoundRobin轮询算法,什么是轮询算法?如果订单服务对库存发起十次请求,那就先让你请求第一台机器,然后是第二台机器,第三台机器,…接着循环到第十台机器
此外,Ribbon是和Feign以及Eureka紧密协作,完成工作的,具体如下:
1.首先Ribbon会从 Eureka Client里面获取到对应的服务注册表,也就知道了所有的服务都部署在了那台机器上,在监听哪些端口,
2.然后Ribbon就可以使用默认的Round Robin算法,从中选择一台机器
3.Feigin就会针对这些机器构造并发送请求
Feign Client会在底层根据你的注解,跟你指定的服务建立连接、构造请求、发起靕求、获取响应、解析响应,等等
Feign的一个机制就是使用了动态代理,
1.首先,如果你对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理接着你要是调用那个接口,本质就是会调用 Feign创建的动态代理,这是核心中的核心
2.Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址
3.最后针对这个地址,发起请求、解析响应
Hystrix是隔离、熔断以及降级的一个框架
https://blog.csdn.net/awodwde/article/details/118964619?
Feign的使用
达到的效果:用户访问feign接口,由feign将请求派发到具体的某一个服务上,并且feign整合了负载均衡
当我们直接访问服务的提供方8001,访问的是它的controller的某个方法
当我们访问的是feign的接口时,我们访问的是80端口,由feign提供访问其他服务器的功能
feign的调用其他服务接口的功能就在@FeignClient注解中实现,参数value指的就是需要被代理的服务的名称
总结: Feign集成了Ribbon,默认以轮询的方式调用微服务实例,简单方便的实现了负载均衡的功能
关于Hystrix
实现的效果:当我们通过消费者的服务器去访问服务提供者的服务器,当数据信息是不存在时,我们给消费者一个友善的提醒,防止重复对服务提供者的服务器造成大量不必要的访问
当我们通过消费者端向服务端发起访问,当查询的部门的id是存在的,则正常返回部门的数据信息
当部门的数据信息查询不存在时,那么将返回fallback服务降级
下图查询部门编号为7的部门,数据是null,则进入熔断方法
服务提供端的服务器的代码如下:在controller层对接口的访问添加了注解@HystrixCommand,并且指定了熔断调取的方法 processHystrix_Get()
熔断的操作可以在原来的服务提供方上进行升级,不需要说重新创建一个module(因为我在第一次学习时,教程上是重新创建了一个module,我以为需要将两者同时运行才能起到服务熔断的效果),然后再添加服务熔断的方法
服务降级是整体资源快不够了,忍痛将某些服务先关掉,待度过难关,在开启回来。
服务降级处理时在客户端完成,与服务端没有关系
服务降级是不是在 feign的基础上延伸的????
在服务提供方的feign的接口基础上,增加服务降级的类需要继承FallbackFactory
@HystrixCommand(fallbackMathod = "降级方法名")
https://blog.csdn.net/weixin_35931314/article/details/113087747
动态代理
aop是ioc的一个扩展功能,先有ioc,再有aop,只是在ioc的整个流程中新增的一个可扩展点而已:beanPostProcessor
总:aop概念,应用场景,动态代理
分:
bean的创建过程中有一个步骤可以对bean进行扩展实现,aop本身就是一个扩展功能,所以在beanPostProcess的后置处理方法中来进行实现
1.代理对象的创建过程(advice,切面,切点)
2.通过jdk或者cglib的方式来生成代理对象
3.在执行方法调用的时候,会调用到生成的字节码文件中,直接找到dynamicadvisedinterceptor类中的的回调入口,即intercept方法,从此方法开始执行
4.根据之前定义好的通知来生成拦截器链
5.从拦截器链中依次获取每一个通知开始执行,在执行过程中,为了方便找到下一个通知是哪个,会有一个cglibmethodinvocation的对象,找的时候是从-1的位置依次开始查找并执行的
对于不同的AopProxy代理对象生成方式,会使用不同的拦截回调入口。
1、对于JDK的AopProxy代理对象,使用InvocationHandler的invoke回调入口;
2、对于CGLib的AopProxy代理对象,使用的是设置好的callback回调;
在callback回调中,对于AOP实现,是通过DynamicAdvisedInterceptor来完成的,而DynamicAdvisedInterceptor的回调入口是intercept方法。
https://blog.csdn.net/weixin_38192427/article/details/123154267
spring ioc的理解,原理,实现:
总:
1.控制翻转理论思想(原来对象是由使用者控制,有了容器之后,可以把整个对象交给spring容器进行控制;DI(依赖注入:把对应的属性值注入到具体的对象中@Autowrired,populateBean完成属性值得注入))
2.容器(存放具体存储对象,使用map结构存储,在spring中一般使用三级缓存,使用singletonobject存储完整的bean对象,整个bean的生命周期,从创建到消亡都是由容器管理(bean的生命周期))
分:
1.容器创建(beanFactory, defaultListableBeanFactory):向bean工厂中设置一些参数(BeanPostProcess,Aware接口子类)等等属性
2.加载解析bean对象:准备创建bean对象的定义对象(beanDefinition)(该过程设置xml或者注解的解析过程)
3.beanFactoryPostProcess的处理(这个是扩展点):placeHolderConfigurSupport处理占位符,configurationClassPostProcess完成具体的扩展功能
4.beanPostProcess的注册功能,方便后续对bean对象进行具体的扩展功能
5.通过反射的方式将beanDefinition实例为具体的bean对象
6.bean对象的初始化过程(填充属性,调用aware接口子类,调用beanPostProcess前置处理方法,调用init-method方法,调用beanPostProcess后置处理方法)
7.生成完整的bean对象,可以通过getBean()直接获取
8.销毁过程
1.反射
2.工厂
3.设计模式
4.关键方法(createBeanFactory,getbean,dogetBean,createbean,doCreateBean,createBeanInstance(getDeclaredConstructor(),newinstance),populateBean,initializingBean)
例如:1. 通过createBeanFactory创建bean工厂(defaultListableBeanFactory)2. 先开始循环创建对象,容器中的bean默认都是单例的,优先通过getBean,doGetBean从容器中查找,找不到的话 3. 通过createBean,doCreateBean方法,以反射的方式创建,一般情况下使用的是无参的构造方法(getDeclaredConstructor(),newinstance())4. 进行对象的属性填充(populateBean) 5. 进行其它的初始化操作(initializingBean)
1.实例化bean(反射)
2.设置属性(循环依赖)
3.调用Aware接口相关的方法(invokeAwaremethod(),完成beanName,beanFactory,beanClassLoader对象属性的设置)
4.调用BeanPostProcess前置处理方法(使用比较多的是applicationContextPostProcess,设置applicationContext,environment,resourceLoader等对象)
5.调用initmethod方法(invokeInitmethod(),先判断是否实现了initializingBean接口,如果有,调用afterpropertiesSet方法,没有就不调用)
6.调用beanPostProcess的后置处理方法(spring的aop就是在这个地方实现的,abstractautoProxyCreator)
7.注册Destuction相关的回调(钩子函数)
8.接口获取到完整的对象,可以通过getBean的方式来进行对象的获取
9.销毁流程(判断是否实现了DispoableBean接口,调用destroyMethod方法)
解决循环依赖:(三级缓存,提前暴露对象,aop)
总:
介绍循环依赖即a依赖吧,b依赖a
分:
bean创建过程:实例化,初始化(填充属性)即先创建A对象,实例化A对象,此时A对象中的b属性为空;从容器中查找,找到了直接赋值(当前情况不存在循环依赖问题,不存在),找不到直接创建B对象;实例化B对象,此时B对象中a属性为空,填充属性a;从容器中创建A对象,没有则创建,形成闭环. 此时A对象是存在的,只不过此时的A对象不是一个完整的状态,只完成了实例化但未完成初始化,如果在程序调用过程中,拥有了某个对象的引用,能在后期完成赋值操作,可以优先吧非完整的对象优先赋值,等待后续操作来完成赋值,相当于提前暴露不完整的对象的引用,,所以解决问题的核心在于实例化和初始化分开操作,这也是解决循环依赖的关键,当所有对象完成实例化和初始化操作之后,还要把完整的对象放到容器中。此时容器中存在对象的几个状态,完成实例化未完整初始化,完整状态,因为都在容器中,所有要使用不同的map结构来进行存储,就有了一级缓存和二级缓存,如果一级缓存中有了,那么为二级缓存中就不会存在同名的对象,因为查找顺序是1,2,3的方式查找。一级缓存存放完整的对象,二级缓存放非完整的对象,为什么需要三级缓存,三级缓存存value类型是ObjectFactory,是一个函数式接口,存在的意义是保证整个容器运行过程中同名的bean对象只能有一个
https://blog.csdn.net/ZHAOJING1234567/article/details/90437192/
https://huaweicloud.csdn.net/a/62a2fa2e78f1bc4bebc13bfc?
依赖注入就是通过spring将bean所需要的一些参数传递到bean实例对象的过程(将依赖关系注入到对象中)
Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:构造方法注入,setter注入,基于注解的注入。
构造方法注入
先简单了解一下测试项目的结构,用maven构建的,四个包:
entity:存储实体,里面只有一个User类
dao:数据访问,一个接口,两个实现类
service:服务层,一个接口,一个实现类,实现类依赖于IUserDao
test:测试包
在spring的配置文件中注册UserService,将UserDaoJdbc通过constructor-arg标签注入到UserService的某个有参数的构造方法
<!-- 注册userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
<constructor-arg ref="userDaoJdbc"></constructor-arg>
</bean>
<!-- 注册jdbc实现的dao -->
<bean id="userDaoJdbc" class="com.lyu.spring.dao.impl.UserDaoJdbc"></bean>
如果只有一个有参数的构造方法并且参数类型与注入的bean的类型匹配,那就会注入到该构造方法中。
public class UserService implements IUserService {
private IUserDao userDao;
public UserService(IUserDao userDao) {
this.userDao = userDao;
}
public void loginUser() {
userDao.loginUser();
}
}
@Test
public void testDI() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取bean对象
UserService userService = ac.getBean(UserService.class, "userService");
// 模拟用户登录
userService.loginUser();
}
测试打印结果:jdbc-登录成功
注:模拟用户登录的loginUser方法其实只是打印了一条输出语句,jdbc实现的类输出的是:jdbc-登录成功,mybatis实现的类输出的是:mybatis-登录成功。
问题一:如果有多个有参数的构造方法并且每个构造方法的参数列表里面都有要注入的属性,那userDaoJdbc会注入到哪里呢?
public class UserService implements IUserService {
private IUserDao userDao;
private User user;
public UserService(IUserDao userDao) {
System.out.println("这是有一个参数的构造方法");
this.userDao = userDao;
}
public UserService(IUserDao userDao, User user) {
System.out.println("这是有两个参数的构造方法");
this.userDao = userDao;
this.user = user;
}
public void loginUser() {
userDao.loginUser();
}
}
结果:会注入到只有一个参数的构造方法中,并且经过测试注入哪一个构造方法与构造方法的顺序无关
问题二:如果只有一个构造方法,但是有两个参数,一个是待注入的参数,另一个是其他类型的参数,那么这次注入可以成功吗?
public class UserService implements IUserService {
private IUserDao userDao;
private User user;
public UserService(IUserDao userDao, User user) {
this.userDao = userDao;
this.user = user;
}
public void loginUser() {
userDao.loginUser();
}
}
结果:失败了,即使在costract-arg标签里面通过name属性指定要注入的参数名userDao也会失败.
问题三:如果我们想向有多个参数的构造方法中注入值该在配置文件中怎么写呢?
public class UserService implements IUserService {
private IUserDao userDao;
private User user;
public UserService(IUserDao userDao, User user) {
this.userDao = userDao;
this.user = user;
}
public void loginUser() {
userDao.loginUser();
}
}
参考写法:通过name属性指定要注入的值,与构造方法参数列表参数的顺序无关。
<!-- 注册userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
<constructor-arg name="userDao" ref="userDaoJdbc"></constructor-arg>
<constructor-arg name="user" ref="user"></constructor-arg>
</bean>
<!-- 注册实体User类,用于测试 -->
<bean id="user" class="com.lyu.spring.entity.User"></bean>
<!-- 注册jdbc实现的dao -->
<bean id="userDaoJdbc" class="com.lyu.spring.dao.impl.UserDaoJdbc"></bean>
问题四:如果有多个构造方法,每个构造方法只有参数的顺序不同,那通过构造方法注入多个参数会注入到哪一个呢?
public class UserService implements IUserService {
private IUserDao userDao;
private User user;
public UserService(IUserDao userDao, User user) {
System.out.println("这是第二个构造方法");
this.userDao = userDao;
this.user = user;
}
public UserService(User user, IUserDao userDao) {
System.out.println("这是第一个构造方法");
this.userDao = userDao;
this.user = user;
}
public void loginUser() {
userDao.loginUser();
}
}
结果:哪个构造方法在前就注入哪一个,这种情况下就与构造方法顺序有关。
setter注入
配置文件如下:
<!-- 注册userService -->
<bean id="userService" class="com.lyu.spring.service.impl.UserService">
<!-- 写法一 -->
<!-- <property name="UserDao" ref="userDaoMyBatis"></property> -->
<!-- 写法二 -->
<property name="userDao" ref="userDaoMyBatis"></property>
</bean>
<!-- 注册mybatis实现的dao -->
<bean id="userDaoMyBatis" class="com.lyu.spring.dao.impl.UserDaoMyBatis"></bean>
注:上面这两种写法都可以,spring会将name值的每个单词首字母转换成大写,然后再在前面拼接上"set"构成一个方法名,然后去对应的类中查找该方法,通过反射调用,实现注入。
切记:name属性值与类中的成员变量名以及set方法的参数名都无关,只与对应的set方法名有关,下面的这种写法是可以运行成功的
public class UserService implements IUserService {
private IUserDao userDao1;
public void setUserDao(IUserDao userDao1) {
this.userDao1 = userDao1;
}
public void loginUser() {
userDao1.loginUser();
}
}
还有一点需要注意:如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。
基于注解的注入
在介绍注解注入的方式前,先简单了解bean的一个属性autowire,autowire主要有三个属性值:constructor,byName,byType。
constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。
byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。
byType:查找所有的set方法,将符合符合参数类型的bean注入。
下面进入正题:注解方式注册bean,注入依赖
主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
@Component:可以用于注册所有bean
@Repository:主要用于注册dao层的bean
@Controller:主要用于注册控制层的bean
@Service:主要用于注册服务层的bean
描述依赖关系主要有两种:
@Resource:java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。
@Resource
@Qualifier("userDaoMyBatis")
private IUserDao userDao;
public UserService(){
}
@Autowired:spring注解,默认是以byType的方式去匹配类型相同的bean,如果只匹配到一个,那么就直接注入该bean,无论要注入的 bean 的 name 是什么;如果匹配到多个,就会调用DefaultListableBeanFactory 的 determineAutowireCandidate 方法来决定具体注入哪个bean。determineAutowireCandidate 方法的内容如下:
// candidateBeans 为上一步通过类型匹配到的多个bean,该 Map 中至少有两个元素。
protected String determineAutowireCandidate(Map<String, Object> candidateBeans, DependencyDescriptor descriptor) {
// requiredType 为匹配到的接口的类型
Class<?> requiredType = descriptor.getDependencyType();
// 1. 先找 Bean 上有@Primary 注解的,有则直接返回
String primaryCandidate = this.determinePrimaryCandidate(candidateBeans, requiredType);
if (primaryCandidate != null) {
return primaryCandidate;
} else {
// 2.再找 Bean 上有 @Order,@PriorityOrder 注解的,有则返回
String priorityCandidate = this.determineHighestPriorityCandidate(candidateBeans, requiredType);
if (priorityCandidate != null) {
return priorityCandidate;
} else {
Iterator var6 = candidateBeans.entrySet().iterator();
String candidateBeanName;
Object beanInstance;
do {
if (!var6.hasNext()) {
return null;
}
// 3. 再找 bean 的名称匹配的
Entry<String, Object> entry = (Entry)var6.next();
candidateBeanName = (String)entry.getKey();
beanInstance = entry.getValue();
} while(!this.resolvableDependencies.values().contains(beanInstance) && !this.matchesBeanName(candidateBeanName, descriptor.getDependencyName()));
return candidateBeanName;
}
}
}
determineAutowireCandidate 方法的逻辑是:
先找 Bean 上有@Primary 注解的,有则直接返回 bean 的 name。
再找 Bean 上有 @Order,@PriorityOrder 注解的,有则返回 bean 的 name。
最后再以名称匹配(ByName)的方式去查找相匹配的 bean。
可以简单的理解为先以 ByType 的方式去匹配,如果匹配到了多个再以 ByName 的方式去匹配,找到了对应的 bean 就去注入,没找到就抛出异常。
还有一点要注意:如果使用了 @Qualifier 注解,那么当自动装配匹配到多个 bean 的时候就不会进入 determineAutowireCandidate 方法(亲测),而是直接查找与 @Qualifer 指定的 bean name 相同的 bean 去注入,找到了就直接注入,没有找到则抛出异常。
tips:大家如果认真思考可能会发现 ByName 的注入方式和 @Qualifier 有点类似,都是在自动装配匹配到多个 bean 的时候,指定一个具体的 bean,那它们有什么不同呢?
ByName 的方式需要遍历,@Qualifier 直接一次定位。在匹配到多个 bean 的情况下,使用 @Qualifier 来指明具体装配的 bean 效率会更高一下。
博主个人觉得:@Qualifer 注解出现的意义或许就是 Spring 为了解决 JDK 自带的 ByName 遍历匹配效率低下的问题。要不然也不会出现两个容易混淆的匹配方式。
写在最后:虽然有这么多的注入方式,但是实际上开发的时候自己编写的类一般用注解的方式注册类,用@Autowired描述依赖进行注入,一般实现类也只有一种(jdbc or hibernate or mybatis),除非项目有大的变动,所以@Qualifier标签用的也较少;但是在使用其他组件的API的时候用的是通过xml配置文件来注册类,描述依赖,因为你不能去改人家源码嘛。
用于测试的项目的网盘链接如下:
https://pan.baidu.com/s/1GrRlT5cLAI3SMu17TA6l6Q
https://blog.csdn.net/cxyzhaosi/article/details/125186916
一、添加 maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
二、定义一个操作日志注解类 OpeLog (需要记录模块、操作类型的可以把注释的解开,因我这里只记录增加和修改的操作记录,所以只要一个操作说明)
package com.tond.zgxsj.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //表示这是一个运行时注解,即运行起来之后
public @interface OpeLog {
//String operModul() default ""; // 操作模块
//String operType() default ""; // 操作类型
String operDesc() default ""; // 操作说明
}
三、创建操作日志切面处理类
package com.tond.zgxsj.config;
import com.hzsparrow.business.base.contant.LoginUserEnum;
import com.hzsparrow.business.base.vo.LoginVO;
import com.tond.zgxsj.entity.ZgxOperationLogEntity;
import com.tond.zgxsj.service.ZgxOperationLogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
@Aspect
@Component
public class OpeLogAspect {
@Autowired
private ZgxOperationLogService zgxOperationLogService;
//定义切点 @Pointcut 在注解的位置切入代码
@Pointcut("@annotation(com.tond.zgxsj.config.OpeLog)")
public void logPoinCut() {
}
@AfterReturning("logPoinCut()")
public void savaOpeLog(JoinPoint joinPoint){
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = attr.getRequest();
HttpSession session = request.getSession(true);
ZgxOperationLogEntity sysLog = new ZgxOperationLogEntity();
//从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
//获取操作
OpeLog opeLog = method.getAnnotation(OpeLog.class);
if (opeLog != null) {
String value = opeLog.operDesc();
sysLog.setOperation(value);//保存获取的操作
}
//获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
//获取请求的方法名
String methodName = method.getName();
//请求的参数
Object[] args = joinPoint.getArgs();
//将参数所在的数组转换成json
String params = Arrays.toString(joinPoint.getArgs());
System.out.println("方法的参数:"+params);
/*StringBuffer rs=new StringBuffer();
String paramClass;
for (Object object2 : args) {
//取到参数的类型
paramClass=object2.getClass().getName();
paramClass=paramClass.substring(paramClass.lastIndexOf(".")+1);
rs.append("[参数,类型]"+paramClass+",值:(id:"
+joinPoint.getArgs()[0]+")");
}*/
//获取用户
LoginVO user = (LoginVO) session.getAttribute(LoginUserEnum.SESSION_USER.getFlag());
sysLog.setUuid(UUID.randomUUID().toString());
String opeDetail = "";
if(user != null){
sysLog.setHsuId(user.getUserId());
sysLog.setHsuAccount(user.getAccount());
sysLog.setHsuName(user.getUserName());
opeDetail = "当前登陆人:{"+user.getUserName()+"},类名:{"+className+"},方法名:{"+methodName+"},参数:{"+params+"}";
//请求的时间
sysLog.setOpeTime(new Date());
//获取用户ip地址
if(session.getAttribute("userIP") != null ){
sysLog.setHsuIp(session.getAttribute("userIP").toString());
}
sysLog.setOpeDetail(opeDetail);
//调用service保存SysLog实体类到数据库
zgxOperationLogService.insertOperationLog(sysLog);
}
}
}
四、最后在需要记录日志的Controller上添加注解
/**
* 创建
*
* @param entity
* @return
*/
@OpeLog(operDesc="添加直管县信息")
@RequestMapping("/save")
public ResultDTO<Object> save(@Validated({Create.class, Default.class}) ZgxInfoEntity entity) {
return zgxInfoService.save(entity, getSessionUser());
}
/**
* 修改
*
* @param entity
* @return
*/
@OpeLog(operDesc="修改直管县信息")
@RequestMapping("/edit")
public ResultDTO<Object> edit(@Validated({Edit.class, Default.class}) ZgxInfoEntity entity) {
return zgxInfoService.edit(entity, getSessionUser());
}
https://www.cnblogs.com/wangcp-2014/p/11553102.html
https://blog.csdn.net/Yanzy_/article/details/107887404
spring AOP的使用,分三个步骤,记住这三个步骤,AOP就不会有问题:
1. 确定目标对象(target—>bean) 通俗的来讲就是“哪个方法需要增强,你就把他交给spring。
2. 编写Advice通知方法 (增强代码) 就是写增强代码
3. 配置切入点和切面 第三点的作用就是:让你的增强代码作用于你要增强的目标对象上
SpringAOP有两种实现方式:传统版本和AspectJ。具体操作都能实现业务需求,但是在这里还是希望大家能使用AspectJ,毕竟整体配置起来较为简单、轻量化,而且现在企业几乎都是AspectJ,传统的方法了解一下即可。
1. AOP的核心理念
封装:冗余且不得不执行的代码放到AOP中执行
公式: AOP = 切入点表达式 + 通知方法
2. 切入点表达式
bean(bean的ID) 按照指定的bean名称拦截用户的请求,之后执行通知方法. 只能匹配单个bean对象
within(包名.类名) 可以按照类通配的方式去拦截用户的请求. 控制粒度较粗.
execution(返回值类型 包名.类名.方法名(参数列表)) 方法参数级别 控制粒度较细
@annotation(包名.注解名称) 按照注解的方式去拦截用户请求.
3. 通知方法
前置通知: 主要在 目标方法执行之前执行
后置通知: 在目标方法执行之后执行
异常通知: 在目标方法执行的过程中报了异常之后执行.
最终通知: 无论什么时候都要执行的通知方法.
上述的通知方法,无法控制目标方法是否执行.所以一般"只做记录不做改变"
环绕通知: 一般采用环绕通知 实现对业务的控制.
4. AOP入门案例
//1.将对象交给容器管理
@Component
//2.定义aop切面
@Aspect
public class CacheAOP {
//公式: 切面 = 切入点表达式 + 通知方法.
/**
* @Pointcut 切入点表达式 可以理解为就是一个if判断,只有满足条件,才能执行通知方法.
*/
//@Pointcut("bean(beanid)") //按类匹配,控制的粒度较粗 单个bean
//@Pointcut("within(com.jt.service..*)") //按类匹配,控制的粒度较粗 多个bean
@Pointcut("execution(* com.jt.service..*.*(..))") //细粒度的匹配方式
public void pointCut() {
}
//joinPoint 方法执行切恰好被切入点表达式匹配,该方法的执行就称之为连接点.
@Before("pointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("我是前置通知!!!!");
String typeName =
joinPoint.getSignature().getDeclaringTypeName();
String methodName = joinPoint.getSignature().getName();
Object[] objs = joinPoint.getArgs();
Object target = joinPoint.getTarget();
System.out.println("方法执行的全路径为:"+typeName+"."+methodName);
System.out.println("获取方法参数:"+objs);
System.out.println("获取目标对象:"+target);
}
//添加环绕通知 可以控制目标方法执行 要求添加参数
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("我是环绕通知开始");
try {
//Object result = joinPoint.proceed();
System.out.println("我是环绕通知结束");
return null;
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e);
} //指定目标方法
}
}
https://www.cnblogs.com/fsjohnhuang/p/4078659.html
https://blog.csdn.net/m0_59259076/article/details/122597408
<sql>标签
<where>标签
<choose> 标签
<set>标签
<foreach>标签
<bind>标签
<sql>标签
也叫<sql>片段,在使用sql片段时使用include标签通过sql片段的id进行引用,sql片段的id在当前空间是唯一的,sql片段中也可以写其他的内容,只要符合语法规范都是可以的。示例:
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, name, age, hobby, del_flag, create_time, update_time
</sql>
<select id="listAnimals" resultType="com.zhang.entity.Animal">
select
<include refid="Base_Column_List"></include>
from animal
</select>
(注:<include>可以是单标签的,效果是一样的,我这里使用了双标签~)
根据where其后是否有sql,判断拼接 where,满足条件就拼接,否则不拼接。示例:
<select id="getAnimalByName" resultType="com.zhang.entity.Animal">
select * from animal
<where>
<if test="name!=null and name!=''">
and name = #{name}
</if>
</where>
</select>
类似于Java中的switch分支。只进入一个满足when的条件,如果所有when都不满足,则进入otherwise。示例:
<select id="getAnimalsByNameOrHobby" resultType="com.zhang.entity.Animal">
select * from animal
<choose>
<when test="hobby!=null and hobby!='' and name!=null and name!=''">
hobby = #{hobby} and name = #{name}
</when>
<when test="hobby!=null and hobby!=''">
hobby = #{hobby}
</when>
<otherwise>
name = #{name}
</otherwise>
</choose>
</select>
与where有相似,其后如果存在条件,则拼接set。<set>标签会动态地在行首插入SET关键字,并且自动帮我们去掉多余的逗号,适用于update,示例:
<update id="update">
update animal
<set>
<if test="age!=null and age!=''">
age = #{age}
</if>
<if test="name!=null and name!=''">
name = #{name},
</if>
<if test="hobby!=null and hobby!=''">
hobby = #{hobby}
</if>
</set>
where id = #{id}
</update>
<update id="update">
update animal
<set>
<if test="age!=null and age!=''">
age = #{age}
</if>
<if test="name!=null and name!=''">
name = #{name},
</if>
<if test="hobby!=null and hobby!=''">
hobby = #{hobby}
</if>
</set>
where id = #{id}
</update>
用于遍历List、Map、Array , 属性如下:
collection:指定需要遍历的元素
item:遍历之后的每一项
separator:定义foreach里面语句的分隔符
index:map中代表key,数组中代表数组下标
<select id="listAnimals" resultType="com.zhang.entity.Animal">
SELECT * FROM animal
WHERE id in
<foreach collection="ids" item="id" index="index"
open="(" close=")" separator=",">
#{id}
</foreach>
</select>
mapper层:
List<Animal> getByName(@Param("animalName") String name);
xml映射层:
<select id="getByName" resultType="com.zhang.entity.Animal">
<!--animalName为传过来的参数-->
<!--根据动物名字进行模糊查询-->
<bind name="animalNameLike" value="'%'+ animalName +'%'"/>
select * from animal
<where>
<if test="animalName != null and animalName != ''">
and `name` like #{animalNameLike}
</if>
</where>
</select>
————————————————
版权声明:本文为CSDN博主「m0_59259076」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_59259076/article/details/122597408
https://blog.csdn.net/weixin_44531229/article/details/107076200?
https://blog.csdn.net/qq_38254897/article/details/84961640?
https://zchen.blog.csdn.net/article/details/79520246?
https://blog.csdn.net/wuskzuo/article/details/79186144?
https://www.cnblogs.com/fsjohnhuang/p/4076592.html
Mybatis的Mapper文件中的select、insert、update、delete元素中都有一个parameterType和resultType属性,parameterType属性用于对应的mapper接口方法接受的参数类型,resultType用于指定sql输出的结果类型。
指定sql输出结果类型,总共就两种:
1. 基本数据类型。
2. pojo类类型。mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。如果有多条数据,则分别进行映射,并把对象放到容器List中。所以即使返回是list数组,resultType也是pojo类型
parameterType:
1. MyBatis的传入参数parameterType类型分两种
1. 1. 基本数据类型:int,string,long,Date;
1. 2. 复杂数据类型:类和Map
2. 如何获取参数中的值:
2.1 基本数据类型:#{value}或${value} 获取参数中的值
2.2 复杂数据类型:#{属性名}或${属性名} ,map中则是#{key}或${key}
以下关于resultType与parameterType 的基本使用的区别 :
1、使用resultType :主要针对于从数据库中提取相应的数据出来
2、使用parameterType :主要针对于将信息存入到数据库中 如:insert 增加数据到数据库
https://blog.csdn.net/blood_Z/article/details/123524415
方法一
-- 40为pageCurrent * pageSize,31 应为(pageCurrent - 1) * pageSize
SELECT * FROM
(
SELECT A.*, ROWNUM RN
FROM (SELECT * FROM TABLE_NAME
WHERE 1 = 1 -- 条件
ORDER BY CREATETIME DESC -- 排序
) A
WHERE ROWNUM <= 40
)
WHERE RN >= 31
方法二
SELECT * FROM
(
SELECT A.*, ROWNUM RN
FROM (SELECT * FROM TABLE_NAME) A
)
WHERE RN BETWEEN 31 AND 40
方法一比方法二效率要高很多,查询效率提高主要体现在 WHERE ROWNUM <= 40 这个语句上。
这是由于CBO 优化模式下,Oracle可以将外层的查询条件推到内层查询中,以提高内层查询的执行效率。方法一中,第二层的查询条件WHERE ROWNUM <= 40就可以被Oracle推入到内层查询中,这样Oracle查询的结果一旦超过了ROWNUM限制条件,就终止查询将结果返回了。
方法二中,由于查询条件BETWEEN 31AND 40是存在于查询的第三层,而Oracle无法将第三层的查询条件推到最内层(即使推到最内层也没有意义,因为最内层查询不知道RN代表什么)。在方法二中,Oracle最内层返回给中间层的是所有满足条件的数据,而中间层返回给最外层的也是所有数据。数据的过滤在最外层完成,效率要比方法一要低得多。
注意
由于oracle排序算法问题,如果排序遇到相同的条件,比如时间,会使分页后一页包含前一页的内容,所以这个时候要把方法改成下面这两种。
方法一,order by 加上id主键
SELECT * FROM
(SELECT tt.*,ROWNUM AS RN FROM
(SELECT t.* FROM ${tableName} t
where 1=1 -- 条件
ORDER BY t.createTime DESC,t.id -- 排序) tt
WHERE tt.ROWNUM <= #{pageNum}*#{pageSize}
) rs
WHERE rs.RN >= #{pageNum-1}*#{pageSize}
方法二
SELECT * FROM
(SELECT tt.*,ROWNUM AS RN FROM
(SELECT t.* FROM ${tableName} t
where 1=1 -- 条件
ORDER BY t.createTime DESC -- 排序) tt
) rs
WHERE rs.RN >= #{pageNum-1}*#{pageSize}
and rs.RN <= #{pageNum}*#{pageSize}
https://blog.csdn.net/MCJPAO/article/details/91893173
相同点:
1.Hibernate与MyBatis都可以是通过SessionFactoryBuider由XML配置文件生成SessionFactory,然后由SessionFactory 生成Session,最后由Session来开启执行事务和SQL语句。其中SessionFactoryBuider,SessionFactory,Session的生命周期都是差不多的。
2.Hibernate和MyBatis都支持JDBC和JTA事务处理。
Mybatis优势
MyBatis可以进行更为细致的SQL优化,可以减少查询字段。
MyBatis容易掌握,而Hibernate门槛较高。
Hibernate优势
Hibernate的DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。
Hibernate对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。
Hibernate数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。
Hibernate有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。
https://blog.csdn.net/m0_48680499/article/details/108390955
hql 是hibernate查询语言 hibernate query language 是一种接近sql的查询语言。
hql与sql的区别?
HQL
1.HQL的类名和属性区分大小写,关键字不区分大小写。
2.?占位符从下标0开始计算位置
3.支持:命名参数
4.hql是面向对象的查询语言
SQL
1.SQL的表名和列名不区分大小写。
2.?占位符从顺序1开始计算位置
3.不支持:命名参数
4.SQL是面向结构查询语言
首先项目是maven项目
首先配置pom.xml文件
接下来创建webapp文件夹,在webapp文件夹下创建web.xml
在这个界面中,注意web.xml文件必须在webapp/WEB-INF/web.xml中
然后在resource文件夹中创建applicationContext.xml文件,这个文件是spring框架的配置文件
由于spring和mybatis需要整合在一起,所以spring中除了常规的,还需要配置数据库连接池以及整合mybatis框架
接下来在resource文件夹下创建spring-mvc.xml文件
此文件是spring-mvc的配置文件
然后在resource文件夹下创建mybatis.xml文件,由于mybatis相关配置已经在applicationContext.xml文件中配置过,只需要额外配置日志即可,十分简单
可以将程序分离,额外创建jdbc.properties文件配置jdbc信息
最后一步,配置web.xml文件
OOP思想指的是面向对象编程,面向对象强调对象的“抽象”、“封装”、“继承”、“多态”,相比面向过程该思想专注于通过对象的一些方法去解决问题,不同的功能可能由不同的对象来负责解决。
- 面向过程:
优点:性能比面向对象好,因为类调用时需要实例化,开销比较大,比较消耗资源。
缺点:不易维护、不易复用、不易扩展.- 面向过程:
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 .
缺点:性能比面向过程差
https://blog.csdn.net/qunqunstyle99/article/details/81007712
条件:
1、继承基类。
2、重写。
3、父类引用指向子类对象
重载与重写是 Java 多态性的不同表现。
重写是父类与子类之间多态性的表现,在运行时起作用(动态多态性,譬如实现动态绑定)
而重载是一个类中多态性的表现,在编译时起作用(静态多态性,譬如实现静态绑定)。
https://blog.csdn.net/wang5701071/article/details/108797859
1.explain关键字查看执行计划
2.正确的建立索引
3.SQL语句中IN包含的值不应过多,对于连续的值,可以用between
4.SELECT语句务必指明字段名称
5.只查询一条数据的时候,使用limit 1
6.避免在where子句中对字段进行null值判断
7.避免在where子句中对字段进行表达式操作
8.对于联合索引来说,要遵守最左前缀法则
9.尽量使用inner join,避免left join
如果连接方式是inner join,在没有其他过滤条件的情况下MySQL会自动选择小表作为驱动表,但是left join在驱动表的选择上遵循的是左边驱动右边的原则,即left join左边的表名为驱动表。
10. 注意范围查询语句
对于联合索引来说,如果存在范围查询,比如between、>、<等条件时,会造成后面的索引字段失效。
解决办法: 业务允许的情况下,使用 >= 或者<= 这样不影响索引的使用.
11.不建议使用%前缀模糊查询
12.在 where 子句中使用 or 来连接条件,如果or连接的条件有一方没有索引,将导致引擎放弃使用索引而进行全表扫描
解决办法: 将or连接的双方都建立索引,就可以使用
13.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描
14.字符串类型的字段 查询的时候如果不加引号'' ,会导致自动进行隐式转换,然后索引失效
15.指定查询的索引
explain关键字分析:
explain是非常重要的关键字,要善于运用它. 通过explain我们可以获得以下信息:
表的读取顺序
数据读取操作的操作类型
哪些索引可以使用
哪些索引被实际使用
表之间的引用
每张表有多少行被优化器查询
使用方法:explain + sql语句
并发编程的⽬的就是为了能提⾼程序的执⾏效率提⾼程序运⾏速度,但是并发编程并不总是能提⾼程序运⾏速度的,⽽且并发编程可能会遇到很多问题,⽐如:内存泄漏、上下⽂切换、死锁还有受限于硬件和软件的资源闲置问题。