一、多线程
1. 缓存击穿
缓存击穿,是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。在高并发下,当第一个线程将数据放入缓存中之前,会出现多个线程从数据库中进行查询的现象,即缓存穿透
解决方法:双重判断加同步代码块
固定语法:
if(第一次对值进行判空,为空时继续){
synchronized (this){
对值进行更新
if(第二次对值进行判空,为空时继续){
从数据库中获取值
}
}
}
一个小例子:
public Integer queryAllUserCount() {
BoundValueOperations<Object, Object> ops = redisTemplate.boundValueOps(Const.ALL_USER_COUNT);
Integer allUserCount = (Integer) ops.get();
if (!ObjectUtils.allNotNull(allUserCount)) {
synchronized (this) {
allUserCount = (Integer) ops.get();
if (!ObjectUtils.allNotNull(allUserCount)) {
allUserCount = userMapper.queryAllUserCount();
ops.set(allUserCount);
}
}
}
return allUserCount;
}
2. 线程池
线程池是提供一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁的额外开销,提高了响应的速度。
(1)体系结构
线程池的体系结构:
java.util.concurrent.Executor 负责线程的使用和调度的根接口
|--ExecutorService 子接口: 线程池的主要接口
|--ThreadPoolExecutor 线程池的实现类
|--ScheduledExceutorService 子接口: 负责线程的调度
|--ScheduledThreadPoolExecutor : 继承ThreadPoolExecutor,实现了ScheduledExecutorService
(2)Executors
Executors是一个工具类,用于创建线程池 :
newCachedThreadPool
:用来创建一个可以无限扩大的线程池,适用于服务器负载较轻,执行很多短期异步任务。newFixedThreadPool
:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于可以预测线程数量的业务中,或者服务器负载较重,对当前线程数量进行限制。newSingleThreadExecutor
:创建一个单线程的线程池,适用于需要保证顺序执行各个任务,并且在任意时间点,不会有多个线程是活动的场景。newScheduledThreadPool
:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。newWorkStealingPool
:创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行,适用于大耗时的操作,可以并行来执行
(3)ExecutorService
ExecutorService 对象的一些方法:
submit()
:- 提交Callable对象,需要重写
call()
方法,返回Future - 提交Runnable对象,需要重写
run()
方法,返回Future,
- 提交Callable对象,需要重写
shutdown()
:- 关闭线程池
3. 掉单问题
简单的一个处理:使用定时器去扫描掉单,然后再处理,使用锁解决定时器和支付同时运行的情况(在支付时(创建了订单但未支付完成),定时器同时扫描到了该条数据,使用锁解决冲突)
二、数据库相关
1. cast与decimal
- CAST:进行数据类型转换,AS关键字分隔的源值和目标数据类型,前面是源值,后面是目标数据类型
- DECIMAL:格式化数字(小数点前的数据表示最多显示的总位数,小数点后的数据表示保留几位小数)
例如:求出学生的平均年龄,
select cast(avg(age) as decimal(10,2)) as avgAge from student
2. 业务逻辑
(1)DQL
一个查询语句就是一个完整的业务逻辑,当前台的响应要获取多个不相关的数据值(不同表或者没有关系的数据)的时候,在Controller层要多次调用相应的Service层,分别查出这些不相关的数据。
(2)DML
在Controller层,对于DML,要调用完整的业务,有时候是多个DML语言是一个业务。这时候给方法起的名字一般在控制层是业务名称,在业务层再细化成各个操作的名字
3. resultMap
MyBatis逆向工程生成的 mapper.xml 中使用了<mapper>
标签,如果查询对应的实体类,则在 标签中使用 resultMap属性指定与实体类的映射关系,因为可能存在表中的字段与实体类的字段不同的情况
4. Dao层方法起名字
Dao层方法的名字要细化,以便方法可以复用
5*. Datetime时区问题
java中使用Date对象往MySQL数据库中存储 Datetime 类型的日期,总是少加个时区。
原因:指定数据库的url时,有个参数serverTimezone
是用来指定时区的,而我写的是UTC(世界标准时间),将UTC改为CTT
(Asia&Shanghai)即可。
spring.datasource.url=jdbc:mysql://localhost:3306/p2p?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=CTT
6. 多表联查查出的值的存放位置
可以在含有外键的表对应的实体类中,添加外键对应字段,该字段是外键对应的表的实体类
例如:
学生表和班级表:学生表中存放有班级表的外键,当多表联查时,可以在学生类中添加一个班级类的字段,在mapper.xml文件中,使用<resultMap>
标签和<association>
标签来查出的值与实体类字段进行映射
<resultMap id="BaseResultMap" type="com.wkcto.springboot.model.Student">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="age" jdbcType="INTEGER" property="age"/>
<association property="classRoom" javaType="com.wkcto.springboot.model.ClassRoom">
<id column="id" jdbcType="INTEGER" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
</association>
</resultMap>
7. $()与$(document).ready()
这两个函数的功能是等价的,都是在页面加载完毕后再执行
$(function(){})
//这两个方法是等价的,都是在页面加载完毕后,然后执行
$(document).ready(function(){}) //在文档加载后激活函数
*8. 属性和JS中均定义了某个事件
当html标签的属性和JS中均定义了某个相同的事件,则只会触发属性中定义的事件
9. MyBatis逆向工程
使用MyBatis逆向工程,推荐字段中分隔单词使用下划线"_
",则逆向工程生成的实体类,会自动把下划线去掉,并转换为驼峰命名法,因此,要使用<resultMap>
标签来将表中的字段与实体类中的字段一一映射起来
10*. 数据库乐观锁机制
实现:基于数据版本(Version)记录机制实现
具体可通过给表加一个版本号或时间戳字段实现,当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断当前版本信息与第一次取出来的版本值大小,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据,拒绝更新,让用户重新操作。
11. 数据库中的日期
current_date:只显示日期,2020-01-01
current_time:只显示时间,23:11:49
current_timestamp:显示日期加时间,2020-01-01 23:11:49
三、JS相关
1. 使用正则
常用的正则表达式:https://editor.csdn.net/md?articleId=103735776
在JS中使用正则表达式:
- 语法:
/正则表达式/.test(字符串)
,如果匹配则返回true,否则返回false
2*. $.each()中使用return
jquery 的 each 方法中如果使用了 return true
或者 return
相当于是 continue
,而 return false
相当于是 break
。并不会使方法结束
3*. $().text()
$().text()
会将$() 中所有取出的DOM元素中的文本内容进行拼接,而其它的方法如 $().html()
只会对第一个DOM元素进行操作
4. jQuery选取
$("div[class$=Err]")
:选取所有class属性以Err结尾的div标签$("div[class^=pro]")
:选取所有class属性以pro开头的div标签
5. 对象失去焦点
- 为 id 为 input 的标签绑定失去焦点事件:
$("#input").onblur(function(){});
- 触发 id 为 input 的标签的失去焦点事件:
$("input").blur();
6. JQ中的md5加密
var password = $.md5(loginPassword) //返回加密后的密码
四、一些问题
1. dubbo超时重试问题
在IDEA中进行debug时,因为发生了超时,dubbo自动进行重试,会多次发送请求,如果刚好是往数据库中插入数据,而且还不是在Service层的事务中,那么可能会发生往数据库中重复插入多条相同的数据问题(主键自增)
2. 产品超卖问题
使用数据库乐观锁解决
3. 订单不过夜
生成订单,如果不付款,会在一定时间内过期 ,比如说在15分钟内过期,但是如果是在23点55分生成的订单,则会在 0 点的时候过期,即时间并不够15分钟
五、一些操作
1. 随机
将要随机生成的元素存放在数组中,可以是任何元素,然后使用Math.Random()*array.length
来随机生成数组的下标
注意:java中默认小数转为整数是去掉小数点后的部分,使用Math.round(num)
可以将小数四舍五入进行取整
3. TODO
在IDEA中使用TODO来记录自己还没有完成的功能
4. 脱敏操作
脱敏操作是指:在页面中,将敏感的数据变得不敏感,比如金额,手机号等,使用***
来对原数据进行一定修改,进行脱敏操作
5. 取小数点后两位
-
各种语言通用的一种思路:将小数乘以100,再取整,再将整数除以100
-
java:使用DecimalFormat对象
0:代表一个数字,如果不存在显示0
#:代表一个或多个数字,如果不存在则显示为空DecimalFormat decimalFormat = new DecimalFormat("#.00"); Double d = Double.valueOf(decimalFormat.format(13.13521));//
-
js:toFixed(四舍五入保留的位数):
var num = new Number(12.3863); document.write(num.toFixed(2));//输入出:12.39
6. 日期格式与整数相加
(1)使用Calendar
主要方法:
add(field, amount)
:- field:int类型,指定要加的整数的单位,是常量,Calendar.DATE表示天,
- amount:int类型,指定数量
public static Date getDateByAddDays(Date date, Integer count) {
//日期处理类对象
Calendar instance = Calendar.getInstance();
//设置日期处理类对象的日期值
instance.setTime(date);
//在指定日期上添加天数
instance.add(Calendar.DATE, count);
return instance.getTime();
}
(2)使用Commons-lang3工具包
直接使用 DateUtils 的相关函数即可,例如:
DateUtils.addDays(Date date, int amount)
:在给定的日期上加上指定的天数DateUtils.addMonths(Date date, int amount)
:在给定的上期上加上指定的月数
7. 引入非(本地和中央)仓库的jar包
- groupId/artifactId/version都可以随便填,但是不能有重复
<scope>
标签指定文件的位置,一般为system- 使用
<systemPath>
标签指定本地jar包的位置 ${basedir}
是项目的根目录
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${basedir}/src/main/webapp/WEB-INF/lib/alipay-sdk-java20170324180803.jar</systemPath>
</dependency>
8. 生成全局唯一单号
- 可以使用时间戳加上 redis 全局唯一数字
String s1 = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
Long l2 = redisTemplate.opsForValue().increment("onlyNum", 1L);
String result = s1 + l2;
- 使用电话号码加上 redis 全局唯一数字(将上面的时间戳换成电话号即可)
9. 时序图
要会看时序图,会画时序图,对于复杂的业务逻辑,可以画个时序图,使用Rational工具
实线是请求,虚线是响应
ctrl + d 是删除组件
10. 后台跳转页面
从后台访问其它网站的页面,比如说支付宝的页面,可以使用请求转发,但是请求转发只能是get请求方式,请求参数都在地址栏中,这时候可以采用如下的方式:
- 先跳转到一个自己网站中的一个页面
- 在该页面中使用from表单内加隐藏域,并使用js在页面加载完毕后自动提交的方式实现get转post请求
例如:
<form method="post" action="http://localhost:9094/pay/api/alipay">
<input type="hidden" name="out_trade_no" th:value="${out_trade_no}">
<input type="hidden" name="total_amount" th:value="${total_amount}">
<input type="hidden" name="subject" th:value="${subject}">
</form>
<script>document.forms[0].submit()</script>
六、一些功能
1. 短信验证码进行登录
随机生成的验证码存放在redis中,键是手机号,值是验证码
主要的实现过程:https://blog.csdn.net/zyx1260168395/article/details/103747807
2. 认证功能
当只更新某个字段时,实体类中只需传入需要更新的字段即可,即便是实体类中的字段的值和数据库中的值相同,没必要将其传入,因为虽然值一样,但还是更新了一遍数据库,速度慢
比如认证时要更新用户的姓名和身份证号
思路一:从可以用从Session中取出的user(含有许多信息),加入身份证号和姓名,再传入dao层进行更新,但是虽然只用到了实体类中的id,姓名和身份证号,但是user表中其它的字段也更新了一遍,会造成速度缓慢,所以使用思路二
思路二:新建一个User,只加入需要改的字段即可
3. 登录后返回上次停留的页面
思路:跳往登录页时,在前端获取当前页的网址,当作请求参数传递到后台,然后跳到登录页后,存在登录页的一个隐藏域中,当登录成功时,获取到这个隐藏域中的地址,再跳到这个地址
获取当前页的网址(Thymeleaf):
主要属性:
#httpServletRequest.requestURL
:获取当前页面的URL#httpServletRequest.queryString
:获取当前页面的请求参数,为null时省略
var redirectUrl = [[${#httpServletRequest.requestURL + (#httpServletRequest.queryString == null? "": "?" + #httpServletRequest.queryString)}]];
//这两种方法均可
var redirectUrl = [[${#strings.replace(#httpServletRequest.requestURL + '?' + #httpServletRequest.queryString,"?null","")}]];
4. 投资排行榜功能
使用redis中的zset集合(zset可以用来解决各种排行榜问题),key为电话号码,score为累计投资金额,用户每投资一笔,就往score中加一笔。
使用到的方法:
incrementScore(K, V, delta)
:元素分数增加,delta是增量
rangeWithScores(K,start,end)
:键为K的集合,索引start<=index<=end的元素子集,返回泛型接口(包括score和value),正序
reverseRangeWithScores(K,start,end)
:键为K的集合,索引start<=index<=end的元素子集,返回泛型接口(包括score和value),倒序
ZSetOperations 操作解释:https://www.cnblogs.com/pqy521/p/7009620.html
5. 支付宝网页支付功能
要学会看API文档,支付宝文档:https://docs.open.alipay.com/catalog
6. 生成简单的图片验证码
@RequestMapping(value = "/jcaptcha/captcha")
public void handleCaptchaRequest(HttpServletRequest request, HttpServletResponse response) {
//生成6位随机验证码,这里就简单的使用一个固定的字符串代替了
String captcha = "1Ag5Kw";
try {
//创建字节数组输出流
ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
//创建图片缓存对象,BufferedImage.TYPE_INT_RGB : 表示一个图像,该图像具有整数像素的 8 位 RGB 颜色
BufferedImage bufferedImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
//获取图片的画布
Graphics graphics = bufferedImage.getGraphics();
//设置画布背景色
graphics.setColor(Color.GREEN);
//设置画布填充区域
graphics.fillRect(0, 0, WIDTH, HEIGHT);
//边框区域
graphics.drawRect(1, 1, WIDTH - 2, HEIGHT - 2);
//设置字体颜色
graphics.setColor(Color.red);
//设置字体样式
graphics.setFont(new Font("微软雅黑", Font.ITALIC, 32));
//填充数据
graphics.drawString(captcha, 10, 38);
//将生成的验证码存放到session中
request.getSession().setAttribute(Constants.CAPTCHA, captcha);
ImageIO.write(bufferedImage, "jpeg", jpegOutputStream);
byte[] captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
//将验证码输出到页面
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0L);
response.setContentType("image/jpeg");
ServletOutputStream respOs = response.getOutputStream();
respOs.write(captchaChallengeAsJpeg);
respOs.flush();
respOs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
7. 生成二维码
使用的是Google生成二维码的依赖:
<!-- google生成二维码依赖 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.0.0</version>
</dependency>
生成二维码:
Map<EncodeHintType,Object> map = new HashMap<EncodeHintType, Object>();
//设置字符编码
map.put(EncodeHintType.CHARACTER_SET, "UTF-8");
/*创建一个二维码,
第一个参数是二维码内容,
第二个参数是常量,二维码的编码规则,
第三、四个参数是二维码的长和宽,
最后一个参数是设置的一些参数
*/
BitMatrix encode = new MultiFormatWriter().encode("https://blog.csdn.net/zyx1260168395", BarcodeFormat.QR_CODE, 200, 200, map);
//将二维码转换成图片,写到指定的路径上
Path path = FileSystems.getDefault().getPath("D://", "grcde.jpg");
MatrixToImageWriter.writeToPath(encode, "jpg", path);
//在浏览器使用输出流来将二维码图片响应到前台
OutputStream out = response.getOutputStream();
MatrixToImageWriter.writeToStream(encode, "jpg", out);
七、一些约定俗称
业务层:session;biz
jar包名字中有 source 的是源码包,不带 source 的是编译好的jar包
用例:测试中用的功能点(用例图,测试用例)
商户系统:开发者的系统
渠道、终端:
code:通信标识,10000表示通信成功
请求/响应参数又叫请求/响应报文
timestamp:时间戳
sign:签名
out_trade_no:商户订单号
8. 退出登录
@RequestMapping("/logout")
public Object logout(HttpServletRequest request) {
HttpSession session = request.getSession();
session.invalidate();
return "redirect:/";
}
其它
1. 注解参数
当注解中的参数只有一个 value 属性时,可以省略value不写,当注解中有多个参数时,每个参数都必须指定属性名。
(1)@RequestParam
将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解)
属性:
value
:参数名required
:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错。defaultValue
:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认
2. maven的继承
父模版中的build标签中的内容是不能被子模块继承的
5. jsp中对字符串处理
使用 fn
标签:https://www.cnblogs.com/evolcq/p/3688443.html?utm_source=tuicool&utm_medium=referral
6. templates文件夹
templates文件夹中的文件是受保护的,必须要通过后台才能进行访问
可变长参数
可变长参数可以传递多个值,也可以传递数组(类型要对)