1、抽象类与接口的区别
1.1、成员的区别:
抽象类:
构造方法:有构造方法,用于子类实例化使用。
成员变量:可以是变量,也可以是常量。
成员方法:可以是抽象的,也可以是非抽象的。
接口:
构造方法:没有构造方法
成员变量:只能是常量。默认修饰符:public static final
成员方法:jdk1.7只能是抽象的。默认修饰符:public abstract (推荐:默认修饰符请自己永远手动给出),jdk1.8可以写以default和static开头的具体方法
1.2、类和接口的关系区别:
类与类:
继承关系,只能单继承。可以多层继承。
类与接口:
实现关系,可以单实现,也可以多实现。
类还可以在继承一个类的同时实现多个接口。
接口与接口:
继承关系,可以单继承,也可以多继承。
1.3、体现理念不同:
抽象类里面定义的都是一个继承体系中的共性内容。
接口是功能的集合,是一个体系额外的功能,是暴露出来的规则
当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。
2、内部类是什么,使用场景
2.1、什么是内部类
将一个类定义在一个类里面或一个方法里面
2.2、内部类的种类
成员内部类、静态内部类、匿名内部类、局部内部类
2.2.1 成员内部类
成员内部类是最普通的内部类,成员内部类可以访问外部类的所有属性和方法,但外部类访问内部类的属性和方法,必须实例化内部类,成员内部类不能包含静态的属性和方法。
public class InnerClassTest {
public int outField1 = 1;
protected int outField2 = 2;
int outField3 = 3;
private int outField4 = 4;
public InnerClassTest() {
// 在外部类对象内部,直接通过 new InnerClass(); 创建内部类对象
InnerClassA innerObj = new InnerClassA();
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
System.out.println("其内部类的 field1 字段的值为: " + innerObj.field1);
System.out.println("其内部类的 field2 字段的值为: " + innerObj.field2);
System.out.println("其内部类的 field3 字段的值为: " + innerObj.field3);
System.out.println("其内部类的 field4 字段的值为: " + innerObj.field4);
}
public class InnerClassA {
public int field1 = 5;
protected int field2 = 6;
int field3 = 7;
private int field4 = 8;
// static int field5 = 5; // 编译错误!普通内部类中不能定义 static 属性
public InnerClassA() {
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
System.out.println("其外部类的 outField1 字段的值为: " + outField1);
System.out.println("其外部类的 outField2 字段的值为: " + outField2);
System.out.println("其外部类的 outField3 字段的值为: " + outField3);
System.out.println("其外部类的 outField4 字段的值为: " + outField4);
}
}
public static void main(String[] args) {
InnerClassTest outerObj = new InnerClassTest();
// 不在外部类内部,使用:外部类对象. new 内部类构造器(); 的方式创建内部类对象
// InnerClassA innerObj = outerObj.new InnerClassA();
}
}
2.2.2 静态内部类
静态内部类就是在成员内部类多加了一个 static 关键字。静态内部类只能访问外部类的静态成员变量和方法(包括私有静态)
public class InnerClassTest {
public int field1 = 1;
public InnerClassTest() {
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
// 创建静态内部类对象
StaticClass innerObj = new StaticClass();
System.out.println("其内部类的 field1 字段的值为: " + innerObj.field1);
System.out.println("其内部类的 field2 字段的值为: " + innerObj.field2);
System.out.println("其内部类的 field3 字段的值为: " + innerObj.field3);
System.out.println("其内部类的 field4 字段的值为: " + innerObj.field4);
}
static class StaticClass {
public int field1 = 1;
protected int field2 = 2;
int field3 = 3;
private int field4 = 4;
// 静态内部类中可以定义 static 属性
static int field5 = 5;
public StaticClass() {
System.out.println("创建 " + StaticClass.class.getSimpleName() + " 对象");
// System.out.println("其外部类的 field1 字段的值为: " + field1); // 编译错误!!
}
}
public static void main(String[] args) {
// 无需依赖外部类对象,直接创建内部类对象
// InnerClassTest.StaticClass staticClassObj = new InnerClassTest.StaticClass();
InnerClassTest outerObj = new InnerClassTest();
}
}
2.2.3 匿名内部类
匿名内部类有多种形式,其中最常见的一种形式莫过于在方法参数中新建一个接口对象 / 类对象,并且实现这个接口声明 / 类中原有的方法了:
线程池执行方法execute里面 new Runnable,Runnable就是匿名内部类
public class InnerClassTest {
public int field1 = 1;
protected int field2 = 2;
int field3 = 3;
private int field4 = 4;
public InnerClassTest() {
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
}
// 自定义接口
interface OnClickListener {
void onClick(Object obj);
}
private void anonymousClassTest() {
// 在这个过程中会新建一个匿名内部类对象,
// 这个匿名内部类实现了 OnClickListener 接口并重写 onClick 方法
OnClickListener clickListener = new OnClickListener() {
// 可以在内部类中定义属性,但是只能在当前内部类中使用,
// 无法在外部类中使用,因为外部类无法获取当前匿名内部类的类名,
// 也就无法创建匿名内部类的对象
int field = 1;
@Override
public void onClick(Object obj) {
System.out.println("对象 " + obj + " 被点击");
System.out.println("其外部类的 field1 字段的值为: " + field1);
System.out.println("其外部类的 field2 字段的值为: " + field2);
System.out.println("其外部类的 field3 字段的值为: " + field3);
System.out.println("其外部类的 field4 字段的值为: " + field4);
}
};
// new Object() 过程会新建一个匿名内部类,继承于 Object 类,
// 并重写了 toString() 方法
clickListener.onClick(new Object() {
@Override
public String toString() {
return "obj1";
}
});
}
public static void main(String[] args) {
InnerClassTest outObj = new InnerClassTest();
outObj.anonymousClassTest();
}
}
2.2.4 局部内部类
局部内部类使用的比较少,其声明在一个方法体 / 一段代码块的内部,而且不在定义类的定义域之内便无法使用,其提供的功能使用匿名内部类都可以实现,而本身匿名内部类可以写得比它更简洁,因此局部内部类用的比较少。来看一个局部内部类的小例子:
public class InnerClassTest {
public int field1 = 1;
protected int field2 = 2;
int field3 = 3;
private int field4 = 4;
public InnerClassTest() {
System.out.println("创建 " + this.getClass().getSimpleName() + " 对象");
}
private void localInnerClassTest() {
// 局部内部类 A,只能在当前方法中使用
class A {
// static int field = 1; // 编译错误!局部内部类中不能定义 static 字段
public A() {
System.out.println("创建 " + A.class.getSimpleName() + " 对象");
System.out.println("其外部类的 field1 字段的值为: " + field1);
System.out.println("其外部类的 field2 字段的值为: " + field2);
System.out.println("其外部类的 field3 字段的值为: " + field3);
System.out.println("其外部类的 field4 字段的值为: " + field4);
}
}
A a = new A();
if (true) {
// 局部内部类 B,只能在当前代码块中使用
class B {
public B() {
System.out.println("创建 " + B.class.getSimpleName() + " 对象");
System.out.println("其外部类的 field1 字段的值为: " + field1);
System.out.println("其外部类的 field2 字段的值为: " + field2);
System.out.println("其外部类的 field3 字段的值为: " + field3);
System.out.println("其外部类的 field4 字段的值为: " + field4);
}
}
B b = new B();
}
// B b1 = new B(); // 编译错误!不在类 B 的定义域内,找不到类 B,
}
public static void main(String[] args) {
InnerClassTest outObj = new InnerClassTest();
outObj.localInnerClassTest();
}
}
2.3、内部类作用
2.3.1、内部类可以很好的实现隐藏
非内部类是不可以使用 private和 protected修饰的,但是内部类却可以,从而达到隐藏的作用。同时也可以将一定逻辑关系的类组织在一起,增强可读性。
2.3.2、间接的实现多继承。
每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。所以说内部类间接的实现了多继承。
3、类冲突现象 如何解决
3.1、不同包下的相同类名冲突
直接修改其中一个类名
3.2、maven jar包冲突
打开pom.xml filter搜索冲突的依赖,<exclusions>排除冲突的依赖,maven update刷新
4、反射是什么 使用场景
4.1、什么是反射
程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性与方法,本质是jvm获取class对象之后,再通过class对象进行反编译,从而获取对象的信息。
// 通过反射机制实例化对象
Class c = Class.forName(className);
Object obj = c.newInstance();
System.out.println(obj);
4.2、反射的使用场景
1. 反编译.class-->.java
2. 通过反射机制访问java对象的属性,方法,构造方法等
- JAVA反射机制获取实体类对象的属性和数据类型以及属性值
/**
* 遍历实体类的属性和数据类型以及属性值
* @param model
* @throws NoSuchMethodException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
public static void reflectTest(Object model) throws NoSuchMethodException,
IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
// 获取实体类的所有属性,返回Field数组
Field[] field = model.getClass().getDeclaredFields();
// 遍历所有属性
for (int j = 0; j < field.length; j++) {
// 获取属性的名字
String name = field[j].getName();
// 将属性的首字符大写,方便构造get,set方法
name = name.substring(0, 1).toUpperCase() + name.substring(1);
// 获取属性的类型
String type = field[j].getGenericType().toString();
// 如果type是类类型,则前面包含"class ",后面跟类名
System.out.println("属性为:" + name);
if (type.equals("class java.lang.String")) {
Method m = model.getClass().getMethod("get" + name);
// 调用getter方法获取属性值
String value = (String) m.invoke(model);
System.out.println("数据类型为:String");
if (value != null) {
System.out.println("属性值为:" + value);
} else {
System.out.println("属性值为:空");
}
}
if (type.equals("class java.lang.Integer")) {
Method m = model.getClass().getMethod("get" + name);
Integer value = (Integer) m.invoke(model);
System.out.println("数据类型为:Integer");
if (value != null) {
System.out.println("属性值为:" + value);
} else {
System.out.println("属性值为:空");
}
}
if (type.equals("class java.lang.Short")) {
Method m = model.getClass().getMethod("get" + name);
Short value = (Short) m.invoke(model);
System.out.println("数据类型为:Short");
if (value != null) {
System.out.println("属性值为:" + value);
} else {
System.out.println("属性值为:空");
}
}
if (type.equals("class java.lang.Double")) {
Method m = model.getClass().getMethod("get" + name);
Double value = (Double) m.invoke(model);
System.out.println("数据类型为:Double");
if (value != null) {
System.out.println("属性值为:" + value);
} else {
System.out.println("属性值为:空");
}
}
if (type.equals("class java.lang.Boolean")) {
Method m = model.getClass().getMethod("get" + name);
Boolean value = (Boolean) m.invoke(model);
System.out.println("数据类型为:Boolean");
if (value != null) {
System.out.println("属性值为:" + value);
} else {
System.out.println("属性值为:空");
}
}
if (type.equals("class java.util.Date")) {
Method m = model.getClass().getMethod("get" + name);
Date value = (Date) m.invoke(model);
System.out.println("数据类型为:Date");
if (value != null) {
System.out.println("属性值为:" + value);
} else {
System.out.println("属性值为:空");
}
}
if (type.equals("double")) {
Method m = model.getClass().getMethod("get" + name);
double value = (double) m.invoke(model);
System.out.println("数据类型为:double");
if (value >0) {
System.out.println("属性值为:" + value);
} else {
System.out.println("属性值为:空");
}
}
}
}
setter与getter方法拼装与调用
// 拼装setter方法
Method m1 = hr.getClass().getMethod("setDay"+day,Double.class);
// 拼装getter方法
Method m2 = hr.getClass().getMethod("getDay"+day);
// 调用getter方法
Double da = (Double) m2.invoke(hr);
// 调用setter方法
m1.invoke(hr,da);
- JAVA反射机制实现调用类的方法
package com.nrxt.nms.mon.ms.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.nrxt.nms.mon.ms.dao.NmsAppConfDao;
import com.nrxt.nms.mon.ms.utils.ByteArrayUtil;
import com.nrxt.nms.mon.ms.utils.DrillUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@Service
public class NmsMonitorDrillService {
@Resource
NmsAppConfDao nmsAppConfDao;
@Autowired
private RestTemplate restTemplate;
@Autowired
private ApplicationContext applicationContext;
private static final Logger logger = Logger.getLogger(NmsMonitorDrillService.class);
public String queryByDrillSend(String param) {
JSONObject requestParam = JSONObject.parseObject(param);
JSONObject requestHead = requestParam.getJSONObject("head");
String bgId = requestHead.getString("bgId");
if (StringUtils.isEmpty(bgId)) {
return "bgId can not be null";
}
String corpCode = requestHead.getString("corpCode");
if (StringUtils.isEmpty(corpCode)) {
return "corpCode can not be null";
}
String drillUrl = "http://127.0.0.1:30098/monitor/interface/queryByDrillReceive";
drillUrl = nmsAppConfDao.queryBgPathByBgCode(corpCode);
byte[] compressedDatas = ByteArrayUtil.objectToByteArray(param, logger);
String uploadResult = DrillUtils.tryUpload(compressedDatas, drillUrl,restTemplate ,logger).toString();
return uploadResult;
}
public String queryByDrillReceive(String param){
JSONObject requestParam = JSONObject.parseObject(param);
JSONObject requestHead = requestParam.getJSONObject("head");
String result = null;
// 获取类的全路径以及名称
String className = requestHead.get("className").toString();
if(!className.contains(".")){
className = "com.nrxt.nms.mon.ms.controller." + className;
}
// 获取方法名
String functionName = requestHead.get("functionName").toString();
try {
Object classBean = applicationContext.getBean(Class.forName(className));
// 获取class文件
Class<?> clazz = classBean.getClass();
// 获取该类所需求的方法
Method method = clazz.getMethod(functionName, HttpServletResponse.class,String.class);
result = method.invoke(classBean,null,param).toString();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return result;
}
}
5、什么是AOP
5.1、AOP定义
面向切面编程,利用AOP可以对业务逻辑 的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高 了开发的效率。
业务逻辑功能抽取出来,然后动态把这个功能切入到需要的方法(或行为)中,需要的才切入,这样便于减少系统的重复代码,降低模块间的耦合度。
5.2、使用场景
- 日志记录
- 权限验证(SpringSecurity有使用)
- 事务控制(调用方法前开启事务, 调用方法后提交关闭事务 )
- 效率检查(检测方法运行时间)
- 数据源代理(seata里面,获取到数据源连接执行的sql)
- 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
5.3、springboot使用代码示例
-
Aspect(切面) === Advice(通知) + PointCut(切入点)
1. 引入AOP依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. MyAdviceConfig配置类
package com.liu.aop.config;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Configuration;
// 表示当前的类是一个配置类
@Configuration
//该注解只能用在类上,作用:代表当前类是一个切面类,等价于spring.xml中的<aop:config>标签
//所以现在有了<aop:config>切面,还需要 通知 + 切入点
// 切面 == 通知 + 切面
@Aspect
public class MyAdviceConfig {
/**
* @param joinPoint
* @Before:前置通知
* value:切入点表达式 二者加起来构建成为一个切面
* JoinPoint:连接点:可以理解为两个圆形的切点,从这个切点就可以获取到当前执行的目标类及方法
* 前置通知和后置通知的参数的都是 JoinPoint, 前置后置通知都没有返回值
*/
// 方法级别:具体到某个具体的方法
// @Before(value = "execution(* com.liu.aop.service.impl.*.*(..))")
// 表示service包下的所有类所有方法都执行该前置通知
@Before(value = "within(com.liu.aop.service.*)")
public void before(JoinPoint joinPoint) {
System.out.println("before开始执行查询.......");
System.out.println("正在执行的目标类是: " + joinPoint.getTarget());
System.out.println("正在执行的目标方法是: " + joinPoint.getSignature().getName());
}
/**
* 后置通知,属性参数同上面的前置通知
* @param joinPoint 前置通知和后置通知独有的参数
*/
@After(value = "execution(* com.liu.aop.service.impl.*.*(..))")
public void after(JoinPoint joinPoint) {
System.out.println("after查询结束.......");
// 获取执行目标类和方法名等等
}
/**
* @param proceedingJoinPoint 环绕通知的正在执行中的连接点(这是环绕通知独有的参数)
* @return 目标方法执行的返回值
* @Around: 环绕通知,有返回值,环绕通知必须进行放行方法(就相当于拦截器),否则目标方法无法
执行
@Around注解可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
*/
@Around(value = "execution(* com.liu.aop.service.impl.*.*(..))")
public Object Around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("--------Around方法开始执行");
//获取入参
Object[] objs = joinPoint.getArgs();
// 获取请求参数方式1
String param = Arrays.toString(objs);
String methodName = ((MethodSignature)
joinPoint.getSignature()).getMethod().getName();
logger.info("方法:{},请求参数:{}",methodName,param )
// 获取请求参数方式2
String[] argNames = ((MethodSignature)
joinPoint.getSignature()).getParameterNames(); // 参数名
Map<String, Object> paramMap = new HashMap<String, Object>();
for (int i = 0; i < objs.length; i++) {
paramMap.put(argNames[i], objs[i]);
}
System.err.println("入参:"+paramMap.toString());
//获取出参
Object result =joinPoint.proceed();
System.err.println("出参:"+result.toString());
return result;
}
}
6、springboot配置文件加载顺序
6.1、配置文件路径的优先级
注:后加载的会覆盖先加载的
–file:./config/
–file:./
–classpath:/config/
–classpath:/
–file:./config/
–file:./
–classpath:/config/
–classpath:/
6.2、配置文件类型的优先级
注:后加载的会覆盖先加载的
- properties
- xml
- yml
- yaml
注:先加载指定环境配置,后加载默认环境配置
- application-xxx.properties
- application.properties
bootstrap和application的区别
- bootstrap配置文件是比application配置文件优先加载的,因为bootstrap是由spring父上下文加载,而application是由子上下文加载
- bootstrap加载的配置信息是不能被application的相同配置覆盖的,如果两个配置文件同时存在,也是以bootstrap为主
- bootstrap常见应用场景:配置一些固定的,不能被覆盖的属性.用于一些系统级别的参数配置,一些需要加密/解密的场景
- application常见应用场景:常用于SpringBoot项目的自动化配置,应用级别的参数配置
7、springCloud 核心组件及作用
springCloud_qq_41482600的博客-CSDN博客
8、jvm参数调优
9、多线程
多线程使用_qq_41482600的博客-CSDN博客_多线程与线程池的运用
9.1、如何安全终止线程,线程安全如何保证,否则
JAVA中有3种方式可以终止正在运行的线程
- 线程正常退出,即run()方法执行完毕了
- 使用Thread类中的stop()方法强行终止线程。但stop()方法已经过期了,不推荐使用,因为stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。
- 使用中断机制interrupt(),推荐使用
9.2、等待线程结束方式
- CountDownLatch
- join
CountDownLatch的使用和原理解析_qq_41482600的博客-CSDN博客
10、定时任务实现方式
java 定时任务实现方式_qq_41482600的博客-CSDN博客
11、数据库
11.1、数据源连接池常用参数,如何调整
11.1.1、配置参数
配置 | 缺省值 | 说明 |
---|---|---|
name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:"DataSource-" + System.identityHashCode(this)。配置此属性版本至少为 1.0.5,低于该版本设置 name 会出错。 | |
url | 连接数据库的 url | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。 | |
driverClassName | 根据url自动识别 | 这一项可配可不配,如果不配置 Druid 会根据 url 自动识别 dbType,然后选择相应的 driverClassName |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经不再使用,配置了也没效果 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock 属性为 true 使用非公平锁。 | |
poolPreparedStatements | false | 是否缓存 preparedStatement,也就是 PSCache。PSCache 对支持游标的数据库性能提升巨大,比如说 oracle。在 mysql 下建议关闭。 |
maxPoolPreparedStatementPerConnectionSize | -1 | 要启用 PSCache,必须配置大于 0,当大于 0 时,poolPreparedStatements 自动触发修改为 true。在 Druid 中,不会存在 Oracle下 PSCache 占用内存过多的问题,可以把这个数值配置大一些,比如说 100 |
validationQuery | 用来检测连接是否有效的 sql,要求是一个查询语句,常用select 'x'。如果 validationQuery 为 null,testOnBorrow、testOnReturn、testWhileIdle 都不会起作用。 | |
validationQueryTimeout | 单位:秒,检测连接是否有效的超时时间。底层调用 jdbc Statement对象的 void setQueryTimeout(int seconds) 方法 | |
testOnBorrow | true | 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。 |
testWhileIdle | false | 建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行validationQuery 检测连接是否有效。 |
keepAlive | false (1.0.28) | 连接池中的 minIdle 数量以内的连接,空闲时间超过 minEvictableIdleTimeMillis,则会执行 keepAlive 操作。 |
timeBetweenEvictionRunsMillis | 1分钟(1.0.14) | 有两个含义: 1) Destroy 线程会检测连接的间隔时间,如果连接空闲时间大于等于 minEvictableIdleTimeMillis 则关闭物理连接。 2) testWhileIdle 的判断依据,详细看 testWhileIdle 属性的说明 |
numTestsPerEvictionRun | 30分钟(1.0.14) | 不再使用,一个 DruidDataSource 只支持一个 EvictionRun |
minEvictableIdleTimeMillis | 连接保持空闲而不被驱逐的最小时间 | |
connectionInitSqls | 物理连接初始化的时候执行的 sql | |
exceptionSorter | 根据dbType自动识别 | 当数据库抛出一些不可恢复的异常时,抛弃连接 |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的 filter:stat 日志用的 filter:log4j 防御 sql 注入的 filter:wall | |
proxyFilters | 类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了 filters 和 proxyFilters,是组合关系,并非替换关系 |
11.1.2、直接使用
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
package com.abc.demo.general.dbpool;
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DruidCase {
public static void main(String[] args) {
DruidDataSource druidDataSource = new DruidDataSource();
Connection connection = null;
try {
druidDataSource.setName("测试连接池");
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://10.40.9.11:3306/mydb?useUnicode=true&characterEncoding=UTF-8");
druidDataSource.setUsername("root");
druidDataSource.setPassword("123456");
druidDataSource.setInitialSize(2);
druidDataSource.setMinIdle(2);
druidDataSource.setMaxActive(5);
druidDataSource.setValidationQuery("select 1");
druidDataSource.setTestWhileIdle(true);
druidDataSource.setTestOnBorrow(true);
druidDataSource.setTestOnReturn(false);
druidDataSource.setMaxWait(6000);
druidDataSource.setFilters("slf4j");
connection = druidDataSource.getConnection();
Statement st = connection.createStatement();
ResultSet rs = st.executeQuery("select version()");
if (rs.next()) {
System.out.println(rs.getString(1));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(connection);
}
//实际使用中一般是在应用启动时初始化数据源,应用从数据源中获取连接;并不会关闭数据源。
druidDataSource.close();
}
private static void close(Connection connection) {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
11.1.3、SpringBoot 中使用
- 引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
- application.yml 配置(单数据源)
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.40.9.11:3306/mydb?useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 2
min-idle: 2
max-active: 5
validation-query: select 1
test-while-idle: true
test-on-borrow: true
test-on-return: false
max-wait: 6000
filter: slf4j
@Autowired
private DataSource dataSource;
- application.yml 配置(多数据源)
spring:
datasource:
druid:
db1:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.140.9.11:3306/mydb?useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
initial-size: 2
min-idle: 2
max-active: 5
validation-query: select 1
test-while-idle: true
test-on-borrow: true
test-on-return: false
max-wait: 6000
filter: slf4j
db2:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.40.9.12:3306/mydb?useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
initial-size: 2
min-idle: 2
max-active: 5
validation-query: select 1
test-while-idle: true
test-on-borrow: true
test-on-return: false
max-wait: 6000
filter: slf4j
>>> 配置数据源类:
package com.abc.demo.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Primary
@Bean("dataSource1")
@ConfigurationProperties("spring.datasource.druid.db1")
public DataSource dataSourceOne(){
return DruidDataSourceBuilder.create().build();
}
@Bean("dataSource2")
@ConfigurationProperties("spring.datasource.druid.db2")
public DataSource dataSourceTwo(){
return DruidDataSourceBuilder.create().build();
}
}
>>> 使用 :
@Autowired
@Qualifier("dataSource1")
private DataSource dataSource1;
@Autowired
@Qualifier("dataSource2")
private DataSource dataSource2;
11.2、mybatis中#{}与${}区别
- #{}是预编译处理,可以防止sql注入,它会将所有传入的参数作为一个字符串来处理
- ${}将传入的参数拼接到sql上去执行,一般用于表名与字段名的参数
11.3、char与varchar区别
- char表示定长,长度固定,varchar表示边长,长度可变;char如果插入的长度小于定义的长度用空格填充,varchar小于定于的长度,按照实际的储存,插入多长存多长;因为长度固定vhar的存取速度比varchar快,但是char也为此付出空间的代价,因为长度固定,所以会占用多余的空间,可谓以空间换取效率,varchar则恰恰相反,以时间换空间。
- 存储的容量不同,对 char 来说,最多能存放的字符个数 255,和编码无关。而 varchar 呢,最多能存放 65532 个字符。varchar的最大有效长度由最大行大小和使用的字符集确定。整体最大长度是 65,532字节。
11.4、union与union all区别
- union会去重并排序;union all直接返回合并结果,不去重与排序
- union all 比union 执行效率高
11.5、优化索引原则
11.5.1、最左前缀匹配原则
多列索引,总是从索引的最前面字段开始,接着往后,中间不能跳过。比如创建了多列索引(name,age,sex),会先匹配name字段,再匹配age字段,再匹配sex字段的,中间不能跳过。mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配。比如a = 1 and b = 2 and c > 3 and d = 4,如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
11.5.2、like语句的前导模糊查询不能使用索引
select * from doc where title like '%XX'; --不能使用索引
select * from doc where title like 'XX%'; --非前导模糊查询,可以使用索引
11.5.3、不要在索引列上面做任何操作(计算、函数),否则会导致索引失效而转向全表扫描
select * from doc where YEAR(create_time) <= '2016';
优化:
select * from doc where create_time <= '2016-01-01';
11.5.4、使用索引与不使用索引
>>> 使用索引:
- 主键自动建立唯一索引。
- 经常作为查询条件在WHERE或者ORDER BY 语句中出现的列要建立索引。
- 查询中与其他表关联的字段,外键关系建立索引。
- 经常用于聚合函数的列要建立索引,如min(),max()等的聚合函数。
>>> 不使用索引:
- 经常增删改的列不要建立索引。
- 有大量重复的列不建立索引。
- 表记录太少不要建立索引,因为数据较少,可能查询全部数据花费的时间比遍历索引的时间还要短,索引就可能不会产生优化效果 。
12、elasticsearch
12.1、什么是倒排索引
关键词到文档ID的映射
12.2、text与keyword类型的区别
- text类型:会分词,先把对象进行分词处理,然后再再存入到es中。当使用多个单词进行查询的时候,当然查不到已经分词过的内容!
- keyword:不分词,没有把es中的对象进行分词处理,而是存入了整个对象!这时候当然可以进行完整地查询!默认是256个字符!
- 推荐使用keyword类型:完全匹配的查询、用于通配符查询
- 推荐使用text类型:使用自动补全的功能、使用搜索系统
12.3、query与filter区别
区别:
- query查出来的结果包括数据表里面符合条件的文档信息以及相关度得分(_score)
- filter查出来的结果要么是数据表里面符合条件的文档信息,要么为null,并且不会计算相关度得分
举个例子:在同一个 index(数据库)下的同一个type(数据表)查询符合条件的name和age.
- query是从数据表中先查出符合name条件的数据作为集合A,再从数据表中查出符合age条件的数据作为集合B,两者再取交集得出最后结果
- filter是从数据表中查出符合name条件的数据作为集合,再使用过滤器直接从该集合中过滤出符合age条件的数据,得出最后结果
使用场景:
- 全文检索和需要计算相关度得分来做排序的场景,使用query
- 其他情况都使用filter过滤查询
性能:
- filter是简单的过滤查询,对集合内的数据只进行满足/不满足条件的筛选,所以处理速度比较快.另外,因为filter过滤的工作原理,每次对文档做相同的过滤操作都会重新启用此信息,所以filter有缓存功能
- query不仅有查询操作,还要计算符合条件的文档的相关度得分,底层操作复杂导致处理速度没有filter快.另外,query的查询结果是不可缓存的
13、mongo
13.1、更新数据不存在时插入数据mongo、mysql、oracle实现
13.1.1、mysql
数据库里执行新增操作时,往往会出现插入的数据在表中已存在的问题,除了在后台代码中对数据进行重复判断外,还可使用SQL来判断
1、存在就更新,不存在就新增
- 使用replace关键字,存在就更新,不存在就新增一条数据表必须有唯一索引unique,且索引所在字段值不能为空。
replace into 'subject' (subjectId,subjectName)
values (1,'离散');
- 根据主键判断是否存在,执行新增(insert)或修改(update)操作;on duplicate key
update为mysql独有语法。
insert into `subject`(subjectId,subjectName)
values('7','离散')
on duplicate key update subjectName='离散数学';
2、存在则不操作,不存在则新增
- insert ignore,必须有唯一键unique。
insert ignore 'subject'(subjectId,subjectName)
values (1,'离散');
- 使用insert…select…语句;使用 dual 做表名可以让你在 select
语句后面直接跟上要插入字段的值,而且这些值还可以不存在当前表中。
INSERT INTO `subject`(subjectId,subjectName)
SELECT 2,'离散数学'
FROM dual
WHERE not exists (select subjectId,subjectName from `subject`
where subjectId='1' and subjectName='离散');
# 与主键无关
INSERT INTO `subject`(subjectName)
SELECT '离散数学'
FROM dual
WHERE not exists (select subjectName from `subject`
where subjectName='离散');
13.1.2、oracle
1、存在就更新,不存在就新增
merge into
13.1.3、mongo
1、存在就更新,不存在就新增
col.update(key, data, {upsert:true});
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
}
)
- query: update的查询条件,类似sql update查询内where后面的。
- update: update的对象和一些更新的操作符,也可以理解为sql update查询内set后面的。
- upsert: 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew, true为插入,默认是false,不插入。
- multi: 可选,mongodb默认是false,只更新找到的第一条记录,如果这个参数为true,则更新所有按条件查出来的多条记录。
- writeConcern: 可选,抛出异常的级别。
13.1.4、MongoDB副本集集群读写分离
- primary:默认参数,只从主节点上进行读取操作;
- primaryPreferred:大部分从主节点上读取数据,只有主节点不可用时从secondary节点读取数据。
- secondary:只从secondary节点上进行读取操作,存在的问题是secondary节点的数据会比primary节点数据“旧”。如果没有可用的从节点,读请求会抛出异常。
- secondaryPreferred:优先从secondary节点进行读取操作,secondary节点不可用时从主节点读取数据;
- nearest:不管是主节点、secondary节点,从网络延迟最低的节点上读取数据。
# MongoDB URI配置 重要,添加了用户名和密码验证
spring.data.mongodb.uri="mongodb://root:123456@192.168.68.138:27017,192.168.68.137:27017,192.168.68.139:27017/apple?slaveOk=true&replicaSet=imac&write=2&readPreference=secondaryPreferred&connectTimeoutMS=300000"
#每个主机的连接数
spring.data.mongodb.connections-per-host=50
#线程队列数,它以上面connectionsPerHost值相乘的结果就是线程队列最大值
spring.data.mongodb.threads-allowed-to-block-for-connection-multiplier=50
spring.data.mongodb.connect-timeout=5000
spring.data.mongodb.socket-timeout=3000
spring.data.mongodb.max-wait-time=1500
#控制是否在一个连接时,系统会自动重试
spring.data.mongodb.auto-connect-retry=true
spring.data.mongodb.socket-keep-alive=true