问题描述
今天在练习数据库增删改查,体验三层架构思想时,随便写了点DAO层代码,但服务器运行时竟然爆出了栈溢出的问题,说实话,空指针问题我还能放着耐心去代码里找找问题,但这个栈溢出,我之前就没有一次解决过,要么改算法,要么直接重写出问题关联的那几段。至于网上说的改JVM的栈内存,这个我还不会。
先说说我遇到的问题吧,请看截图:
类型 异常报告
消息 Servlet执行抛出一个异常
描述 服务器遇到一个意外的情况,阻止它完成请求。
例外情况
javax.servlet.ServletException: Servlet执行抛出一个异常
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
根本原因。
java.lang.StackOverflowError
java.security.AccessController.doPrivileged(Native Method)
org.apache.commons.logging.LogFactory.getContextClassLoaderInternal(LogFactory.java:808)
org.apache.commons.logging.LogFactory.getFactory(LogFactory.java:419)
org.apache.commons.logging.LogFactory.getLog(LogFactory.java:655)
org.springframework.jdbc.support.JdbcAccessor.<init>(JdbcAccessor.java:43)
org.springframework.jdbc.core.JdbcTemplate.<init>(JdbcTemplate.java:164)
com.crud.crud_demo.dao.impl.EmployeeImpl.<init>(EmployeeImpl.java:16)
com.crud.crud_demo.dao.impl.EmployeeImpl.<init>(EmployeeImpl.java:17)
我调用DAO层的EmployeeImpl来完成SQL语句来接收数据库数据,并以List集合返回给上层,最后返回到JSP页面层来显示。这里是我的EmployeeImpl代码:
package com.crud.crud_demo.dao.impl;
import com.crud.crud_demo.dao.EmployeeDao;
import com.crud.crud_demo.domain.Employee;
import com.crud.crud_demo.util.JDBCUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
/**
* 使用SQL语句的地方
*/
public class EmployeeImpl implements EmployeeDao {
//创建JdbcTemplate 对象并用静态工具类获取数据源
private JdbcTemplate jdbcTemplate=new JdbcTemplate(JDBCUtils.getDataSource());
private EmployeeDao employeeDao=new EmployeeImpl();
@Override
public List<Employee> findAll() {
String sql="select * from employee";
List<Employee> list= jdbcTemplate.query(sql, new BeanPropertyRowMapper<Employee>(Employee.class));
return list;
}
}
发生原因
首先我们可以再看看看第一张服务器报错的界面
后面的报错地方全是一样的,说明这不是代码量较多导致变量过多的问题,而是某处存在创建变量的递归或者循环一直在运行。
相信聪明的小伙伴已经找到问题了,没错就是这一句:
private EmployeeDao employeeDao=new EmployeeImpl();
我也不知道这句我是什么时候写上去的,就很神奇。
EmployeeImpl
类的实例化过程中,我通过private EmployeeDao employeeDao=new EmployeeImpl();
这一行代码,又创建了一个新的EmployeeImpl
实例
而这个实例中的private EmployeeDao employeeDao=new EmployeeImpl();
又会创建一个新的EmployeeImpl实例。
就这样无限递归下去,因为每次方法调用都会在调用栈上分配一定的空间,而无限递归会导致调用栈不断增长,直到耗尽可用的栈空间。
活脱脱的一个俄罗斯套娃
能写出这种💩代码试问这个世界上除了我还有谁能做到💩💩💩💩💩💩💩💩💩💩💩💩💩💩
不过借着这个机会了解了一下递归实例化的模式。也算是有点收获。
解决办法
删掉那个套娃语句就行
顺便了解一下什么是递归实例化
递归实例化是一种编程模式,它在某些情况下可能有用,但也存在一些潜在的缺点。
优点:
简洁性:通过递归实例化,您可以使用较少的代码实现复杂的功能。相对于使用循环来处理嵌套结构,递归实例化的代码通常更简洁、易于理解和维护。
可读性:递归实例化可以使代码更加可读和自解释。它可以更直观地表示问题的解决方案,特别是对于涉及嵌套结构的问题。通过递归实例化,您可以将问题分解为更小的子问题,每个子问题都可以用相同的方式解决,从而使代码更具可读性。
灵活性:递归实例化可以应对未知深度的数据结构,因为它不需要提前知道要处理的嵌套层级。这使得递归实例化在处理树形结构、图形结构或其他具有递归性质的问题上非常有用。
缺点:
空间复杂度:递归实例化在函数调用过程中需要使用额外的内存来保存中间结果和函数调用栈。如果递归深度很大,这可能导致栈溢出或消耗大量内存。因此,在使用递归实例化时需要注意控制递归深度,以避免空间复杂度过高的问题。
时间复杂度:由于递归实例化需要进行多次函数调用和返回操作,因此可能会导致较高的时间复杂度。特别是在处理大量数据时,递归实例化可能会导致性能下降。因此,在使用递归实例化时需要注意优化算法以降低时间复杂度。