项目
1.1、商品管理
新增商品
同时插入商品对应的使用时间数据,需要操作两张表:product,product_usetime。在productService接口中定义save方法,该方法接受一张Dto对象,dto对象继承自product类,并将product_usetime的List集合作为成员属性。通过productService实现类的save方法,将dto对象保存至数据库。然后取出dto实体中的商品id,遍历Dto的元素类型为product_usetime的List集合,并将商品id赋给product_usetime对象,最后将product_usetime的集合存入数据库。
代码中使用了 @Transactional
注解,表示该方法应该在事务中执行,即保证整个方法的原子性,如果方法执行过程中出现异常,则会回滚已执行的操作。
商品分类查询
大致思路:构造分页对象,添加查询添加,例如根据name模糊查询还有排序条件,使用商品业务实现类的page方法进行分页查询,得到pageInfo对象,因为pageInfo的records字段存储的商品类中只有分类id,没有分类名称,因此用dto对象来替代records字段存储的商品类,dto类继承自商品类,并且将分类名称作为它的成员属性。
- 因为商品中的分类名称为id值,要去分类表中根据id查询分类名称,因此要用SetmealDto数据传输对象(Data Transfer Object),Dto继承自商品类,并且有分类名称字段。
- 构造分页对象,分别是商品分页构造器对象和商品Dto分页构造器对象。
- 创建LambdaQueryWrapper对象,添加查询条件,根据name进行like模糊查询,添加排序条件,根据更新时间降序排列,调用商品业务实现类的page方法进行分页查询,得到pageInfo对象。
- 对象拷贝,将pageInfo对象拷贝到dtoPage对象,但pageInfo对象的records列表的元素类型为商品类型,而我们需要的元素类型为商品Dto,因此records字段不需要被拷贝。
- 为了使dtoPage的records列表字段的元素类型为商品Dto,我使用流式操作对pageInfo的records列表进行处理。具体处理为创建商品Dto对象,将商品对象拷贝到商品Dto对象,再根据分类id查询分类对象,然后将分类名称赋给SetmealDto对象。
- 处理完成后,得到一个以商品Dto为元素类型的list集合,将该集合赋给dtoPage的records字段,最终将dtoPage对象返回给客户端即可。
1.2、订单管理
操作多张表,在service接口中扩展方法。
业务逻辑如下:
通过ThreadLocal获得用户id,根据用户id获得用户数据和购物车数据,根据客户端传来的收货地址查询地址数据,使用IdWorker.getId() 方法创建一个订单号。
对购物车数据创建一个Stream流,使用map方法对流中的每个元素进行映射操作,设置订单明细对象的值,并计算订单的总额,最终向订单明细表插入数据。
设置订单对象的值,其订单金额由上一步计算得来,最终向订单表插入数据。
最后删除购物车数据。
2、多线程
需求:根据商品表获取商品使用时间表。
- 使用单例设计模式创建
ThreadPoolExecutor
的单例线程池, 用于管理线程的执行。 - 据任务数量和线程池的队列大小来确定每个子任务的大小,确保每个子任务的大小不会超过线程池的队列大小。
- 创建一个
SynchroniseUtil
对象,参数为子任务的数量,用于同步子任务的执行。 - 使用一个循环遍历来划分任务列表
orders
。 - 创建一个
OrderTask
对象,该对象封装了子列表orderVOSubList
、用户列表users
和同步器synchroniseUtil
。 - 使用线程池的
execute
方法提交OrderTask
,使其在线程池中执行。 Task
类实现了Runnable
接口,并重写了run()
方法。run()
方法是在子线程中执行的代码逻辑。其目的是获取对应商品的使用时间数据,并将使用时间数据添加到dto对象,该dto对象继承自商品对象,并且包含使用时间的字段。- 处理结束后将结果添加到同步器
synchroniseUtil
中。 - 同步器
synchroniseUtil
,每个线程调用addResult()
方法后都能将计数器的值减1,当所有线程都完成时,调用get()
方法获取结果。 - SynchroniseUtil 在多线程场景中起到了同步线程并收集结果的作用。
3、AOP输出日志
4、SQL优化
-
为常作为查询条件的字段建立索引,where子句中的列,或者连接子句中指定的列
-
为经常需要排序、分组操作的字段建立索引
-
更新频繁字段不适合创建索引
-
不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
-
对于定义为text、image和bit的数据类型的列不要建立索引,使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间
-
使用覆盖索引:创建覆盖索引,即索引包含查询所需的全部列,可以减少查询的IO操作,提高查询性能。
-
最左前缀原则,就是最左边的优先。指的是联合索引中,优先走最左边列的索引。对于多个字段的联合索引,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的顺序可以任意调整。
-
非空字段:应该指定列为NOT NULL,除非你想存储NULL。
-
不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长
5、过滤器
拦截器和过滤器的区别:
过滤器能够对所有请求进行过滤处理,而拦截器通常只针对特定的控制器或控制器方法进行拦截。
-
执行时机:
- 过滤器(Filter):在Servlet容器中,过滤器是最早执行的组件,它能够在请求进入Servlet容器之前进行处理,也能在响应返回客户端之前进行处理。
- 拦截器(Interceptor):拦截器是在请求进入控制器(Controller)之前或之后进行处理的组件。
-
使用范围:
- 过滤器(Filter):过滤器可以用于对所有的Servlet请求进行过滤处理,包括静态资源(如HTML、CSS、JavaScript文件)和动态请求(如Servlet请求)。
- 拦截器(Interceptor):拦截器通常用于对控制器请求进行处理,即针对特定的控制器或控制器方法进行拦截。
-
所属技术框架:
- 过滤器(Filter):过滤器是Servlet规范的一部分,与具体的框架无关,可用于任何基于Servlet容器的Web应用。
- 拦截器(Interceptor):拦截器通常与特定的Web框架(如Spring MVC)或其他框架集成使用,依赖于框架提供的拦截器机制。
过滤器的实现:
-
通过
@WebFilter
注解将该类声明为一个过滤器,并指定过滤器应用于所有的URL路径("/*")。 -
实现
Filter
接口,并实现其中的doFilter
方法,获取HttpServletRequest
和HttpServletResponse
对象。通过request.getRequestURI()
获取当前请求的URI路径。 -
定义一个包含不需要进行登录检查的请求路径数组
urls,
调用check
方法,将urls
数组和当前请求的URI进行匹配,判断本次请求是否需要进行登录检查。 -
如果不需要进行登录检查,则直接调用
filterChain.doFilter(request, response)
放行请求,将请求传递给目标资源。 -
如果需要进行登录检查,则判断是否登录,如果已登录,则将用户ID存入
ThreadLocal
对象中,然后调用filterChain.doFilter(request, response)
放行请求。 -
如果未登录,则通过输出流的方式向客户端响应一个包含错误信息的JSON字符串,表示未登录状态。
6、全局异常
在全局异常处理器中,使用@ControllerAdvice注解标注该类,并通过annotations属性指定要增强的控制器类型为@RestController和@Controller,即RESTful风格的控制器和普通控制器。
使用@ResponseBody注解将方法的返回值直接作为响应体返回,而不需要额外的视图解析。
定义了一个异常处理方法 exceptionHandler
,用于处理 SQL完整性约束冲突
类型的异常。当捕获到该异常时,会执行该方法进行处理。
处理逻辑如下:
- 首先,使用日志记录异常信息。
- 判断异常信息中是否包含唯一约束条件,如果是,则表示出现了重复的条目,通过解析异常信息获取重复的条目值,并返回自定义错误信息。
- 如果异常信息中不包含唯一约束条件,则返回默认的未知错误信息。
技能
动态代理
-
简要解释动态代理:动态代理是一种在运行时创建代理对象的技术,通过代理对象可以在不修改原始对象的情况下拦截并增强方法的调用。
-
提及动态代理的两种类型:
-
基于接口的动态代理:基于接口的动态代理是通过Java的反射机制在运行时动态创建代理对象。该代理对象实现了目标接口,并将方法调用委托给实际的对象。我会提到
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口在基于接口的动态代理中的重要性。 -
基于类的动态代理:我会解释基于类的动态代理是通过继承和字节码操作在运行时动态创建代理对象。我会提到
java.lang.reflect.Proxy
类和java.lang.reflect.MethodInterceptor
接口在基于类的动态代理中的作用。 -
动态代理的应用场景:我会提及动态代理的一些常见应用场景,如日志记录、事务管理、权限控制等。
设计模式
单例模式
是一种创建型设计模式,用于确保一个类只有一个实例,因此类的构造方法为private并且拥有一个当前类的静态成员变量,并提供对该实例的全局访问点,向外界再提供一个静态方法,向外界提供当前类的实例,当前类只能在内部实例化。序列号生成器、web页面计数器或者创建对象需要消耗较多资源时,可用单例模式。单例模式的最佳实践为无状态的,比如以工具类的形式提供。
项目实践:使用单例模式管理数据库连接对象,避免频繁地创建和销毁连接,提高性能和资源利用率。
懒汉式的双重检查锁:
代理模式
提供了一个代理对象作为另一个对象的替代,以控制对该对象的访问。代理对象充当原始对象的接口,可以提供额外的功能,如延迟初始化、访问控制或缓存。例如在分布式系统中,客户端通过代理对象访问远程服务,代理对象负责网络通信和数据传输。
项目实践:过滤器模式是代理模式的一种特殊情况,它通过代理对象对目标对象进行包装,以在目标对象的方法执行前后添加额外的处理逻辑。
观察者模式
定义了对象之间的一对多依赖关系。当一个对象(主题)的状态发生变化时,所有依赖对象(观察者)都会被通知并自动更新。这种模式实现了松耦合的通信,使对象能够对变化做出反应而无需紧密耦合在一起。例如在数据库系统中,触发器可以通过观察者模式实现,当数据库中的数据发生变化时触发相应的操作。
适配器模式
用于使具有不兼容接口的对象能够共同工作。它提供了一个适配器作为两个不兼容对象之间的桥梁,将它们的接口进行转换,以使它们能够协同工作。在不同的数据格式之间进行转换时,适配器模式可以提供一个统一的接口来适应不同的数据格式。
项目实践:对象映射器(Object Mapper)用于将Java对象与JSON之间进行转换。
Linux常用命令
- ls:列出当前目录的文件和子目录。
- cd:切换目录。
- pwd:显示当前所在的目录。
- mkdir:创建新的目录。
- rm:删除文件或目录。
- cp:复制文件或目录。
- mv:移动文件或目录。
- cat:查看文件内容。
- grep:在文件中查找指定的字符串。
- find:在文件系统中查找文件。
- chmod:修改文件或目录的权限。
- tar:打包和解压文件。
- ssh:远程登录到另一台计算机。
- wget:从网络下载文件。
- ps:查看当前运行的进程。
- kill:终止正在运行的进程。
Bean的生命周期管理
1、实例化bean:反射的方式生成对象。
2、填充bean的属性: 使用populateBean方法将属性值填充到一个 Java 对象,在这里会使用三级缓存来解决循环依赖的问题 。
3、调用aware接口相关的方法: 调用invokeAwareMethods
方法,完成对BeanName,BeanFactory,BeanClassLoader对象的属性设置。
- BeanName指的是当前Bean在容器中的名称。
- BeanFactory指的是当前Bean所在的BeanFactory对象,可以通过它来获取其他Bean实例。
- BeanClassLoader指的是加载当前Bean类的ClassLoader对象。
4、调用BeanPostProcessor中的前置处理方法: 使用比较多的有 ApplicationContextPostProcessor,设置ApplicationContext,Environment等对象,可以在应用上下文初始化的早期对Spring容器进行自定义的配置和扩展,满足应用程序特定的需求。
5、调用initmethod方法:会首先判断是否实现了InitializingBean接口。如果实现了该接口,Spring会自动调用其afterPropertiesSet方法,以执行Bean的初始化逻辑。如果没有实现InitializingBean接口,Spring则不会调用afterPropertiesSet方法。
6、用BeanPostProcessor的后置处理方法: spring的aop就是在此处实现的。
7、获取到完整的对象,可以通过getBean的方式来进行对象的获取
8、在销毁流程中,首先会判断Bean是否实现了DisposableBean接口。如果实现了该接口,Spring会自动调用其destroy方法,以执行Bean的销毁逻辑。如果没有实现DisposableBean接口,Spring则会查看配置中是否指定了destroyMethod方法名,并在销毁时调用该方法。