`

Spring的事务管理难点剖析(7):数据连接泄漏

阅读更多
底层连接资源的访问问题

   对于应用开发者来说,数据连接泄漏无疑是一个可怕的梦魇。只要你开发的应用存在数据连接泄漏的问题,应用程序最终都将因数据连接资源的耗尽而崩溃,甚至还可能引起数据库的崩溃。数据连接泄漏像一个黑洞那样让开发者避之唯恐不及。
   Spring DAO对所有支持的数据访问技术框架都使用模板化技术进行了薄层的封装。只要你的程序都使用Spring DAO的模板(如JdbcTemplate、HibernateTemplate等)进行数据访问,一定不会存在数据连接泄漏的问题——这是Spring给予我们的郑重承诺!如果使用Spring DAO模板进行数据操作,我们无须关注数据连接(Connection)及其衍生品(Hibernate的Session等)的获取和释放操作,模板类已经通过其内部流程替我们完成了,且对开发者是透明的。
   但是由于集成第三方产品、整合遗产代码等原因,可能需要直接访问数据源或直接获取数据连接及其衍生品。这时,如果使用不当,就可能在无意中创造出一个魔鬼般的连接泄漏问题。
我们知道:当Spring事务方法运行时,就产生一个事务上下文,该上下文在本事务执行线程中针对同一个数据源绑定了一个唯一的数据连接(或其衍生品),所有被该事务上下文传播的方法都共享这个数据连接。这个数据连接从数据源获取及返回给数据源都在Spring掌控之中,不会发生问题。如果在需要数据连接时,能够获取这个被Spring管控的数据连接,则使用者可以放心使用,无须关注连接释放的问题。
    那么,如何获取这些被Spring管控的数据连接呢?Spring提供了两种方法:其一是使用数据资源获取工具类;其二是对数据源(或其衍生品如Hibernate SessionFactory)进行代理。

Spring JDBC数据连接泄漏

   如果我们从数据源直接获取连接,且在使用完成后不主动归还给数据源(调用Connection#close()),则将造成数据连接泄漏的问题。
package com.baobaotao.connleak;
…
@Service("jdbcUserService")
public class JdbcUserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void logon(String userName) {
        try {

            //①直接从数据源获取连接,后续程序没有显式释放该连接
            Connection conn = jdbcTemplate.getDataSource().getConnection();
            String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
            jdbcTemplate.update(sql, System.currentTimeMillis(), userName);

            //②模拟程序代码的执行时间
            Thread.sleep(1000); 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

   JdbcUserService通过Spring AOP事务增强的配置,让所有public方法都工作在事务环境中,即让logon()和updateLastLogonTime()方法拥有事务功能。在logon()方法内部,我们在①处通过调用jdbcTemplate.getDataSource().getConnection()显式获取一个连接,这个连接不是logon()方法事务上下文线程绑定的连接,所以如果开发者没有手工释放这个连接(显式调用Connection#close()方法),则这个连接将永久被占用(处于active状态),造成连接泄漏!下面,我们编写模拟运行的代码,查看方法执行对数据连接的实际占用情况:
package com.baobaotao.connleak;
…
@Service("jdbcUserService")
public class JdbcUserService {
    …
    //①以异步线程的方式执行JdbcUserService#logon()方法,以模拟多线程的环境
    public static void asynchrLogon(JdbcUserService userService, String userName) {
        UserServiceRunner runner = new UserServiceRunner(userService, userName);
        runner.start();
    }
    private static class UserServiceRunner extends Thread {
        private JdbcUserService userService;
        private String userName;
        public UserServiceRunner(JdbcUserService userService, String userName) {
            this.userService = userService;
            this.userName = userName;
        }
        public void run() {
            userService.logon(userName);
        }
    }
    
   //②让主执行线程睡眠一段指定的时间
   public static void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

   //③汇报数据源的连接占用情况
    public static void reportConn(BasicDataSource basicDataSource) {
        System.out.println("连接数[active:idle]-[" +
                       basicDataSource.getNumActive()+":"+basicDataSource.getNumIdle()+"]");
    }

    public static void main(String[] args) {
        ApplicationContext ctx = 
         new ClassPathXmlApplicationContext("com/baobaotao/connleak/applicatonContext.xml");
        JdbcUserService userService = (JdbcUserService) ctx.getBean("jdbcUserService");

        BasicDataSource basicDataSource = (BasicDataSource) ctx.getBean("dataSource");
        
        //④汇报数据源初始连接占用情况
        JdbcUserService.reportConn(basicDataSource);

        JdbcUserService.asynchrLogon(userService, "tom");//启动一个异常线程A
        JdbcUserService.sleep(500);

        //⑤此时线程A正在执行JdbcUserService#logon()方法
        JdbcUserService.reportConn(basicDataSource); 

        JdbcUserService.sleep(2000);

        //⑥此时线程A所执行的JdbcUserService#logon()方法已经执行完毕
        JdbcUserService.reportConn(basicDataSource);

        JdbcUserService.asynchrLogon(userService, "john");//启动一个异常线程B
        JdbcUserService.sleep(500);

        //⑦此时线程B正在执行JdbcUserService#logon()方法
        JdbcUserService.reportConn(basicDataSource);
        
        JdbcUserService.sleep(2000);

        //⑧此时线程A和B都已完成JdbcUserService#logon()方法的执行
        JdbcUserService.reportConn(basicDataSource);

    }


    在JdbcUserService中添加一个可异步执行logon()方法的asynchrLogon()方法,我们通过异步执行logon()以及让主线程睡眠的方式模拟多线程环境下的执行场景。在不同的执行点,通过reportConn()方法汇报数据源连接的占用情况。
    通过Spring事务声明,对JdbcUserServie的logon()方法进行事务增强,配置代码如下所示:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
      …
     http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
    <context:component-scan base-package="com.baobaotao.connleak"/>
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close" 
		p:driverClassName="${jdbc.driverClassName}"
		p:url="${jdbc.url}" 
		p:username="${jdbc.username}"
		p:password="${jdbc.password}"/>

    <bean id="jdbcTemplate"
          class="org.springframework.jdbc.core.JdbcTemplate"
          p:dataSource-ref="dataSource"/>

    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
          p:dataSource-ref="dataSource"/>

     <!--①启用注解驱动的事务增强-->
     <tx:annotation-driven/>      
</beans>

然后,运行JdbcUserServie,在控制台将观察到如下的输出信息:
引用
连接数[active:idle]-[0:0]
连接数[active:idle]-[2:0]
连接数[active:idle]-[1:1]
连接数[active:idle]-[3:0]
连接数[active:idle]-[2:1]

我们通过表10-3对数据源连接的占用和泄漏情况进行描述。
时间 执行线程1 执行线程2 数据源连接
                 active idle leak
T0未启动       未启动 0 0 0
T1正在执行方法  未启动 2 0 0
T2执行完毕   未启动 1 1 1
T3执行完毕   正式执行方法 3 0 1
T4执行完毕   执行完毕 2 1 2

   可见在执行线程1执行完毕后,只释放了一个数据连接,还有一个数据连接处于active状态,说明泄漏了一个连接。相似的,执行线程2执行完毕后,也泄漏了一个连接:原因是直接通过数据源获取连接(jdbcTemplate.getDataSource().getConnection())而没有显式释放。

通过DataSourceUtils获取数据连接

    Spring提供了一个能从当前事务上下文中获取绑定的数据连接的工具类,那就是DataSourceUtils。Spring强调必须使用DataSourceUtils工具类获取数据连接,Spring的JdbcTemplate内部也是通过DataSourceUtils来获取连接的。    DataSourceUtils提供了若干获取和释放数据连接的静态方法,说明如下:

  •   static Connection doGetConnection(DataSource dataSource):首先尝试从事务上下文中获取连接,失败后再从数据源获取连接;
  • static Connection getConnection(DataSource dataSource):和doGetConnection方法的功能一样,实际上,它内部就是调用doGetConnection方法获取连接的;
  • static void doReleaseConnection(Connection con, DataSource dataSource):释放连接,放回到连接池中;
  • static void releaseConnection(Connection con, DataSource dataSource):和doRelease Connection方法的功能一样,实际上,它内部就是调用doReleaseConnection方法获取连接的。


来看一下DataSourceUtils从数据源获取连接的关键代码:

public abstract class DataSourceUtils {
…
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
		Assert.notNull(dataSource, "No DataSource specified");

     //①首先尝试从事务同步管理器中获取数据连接
	ConnectionHolder conHolder = 
           (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
	if (conHolder != null && (conHolder.hasConnection() || 
                          conHolder.isSynchronizedWithTransaction())) { 
			conHolder.requested();
			if (!conHolder.hasConnection()) {
				logger.debug("Fetching resumed JDBC Connection from DataSource");
				conHolder.setConnection(dataSource.getConnection());
			}
			return conHolder.getConnection();
		}

          //②如果获取不到连接,则直接从数据源中获取连接
		Connection con = dataSource.getConnection();

         //③如果拥有事务上下文,则将连接绑定到事务上下文中
		if (TransactionSynchronizationManager.isSynchronizationActive()) {
              ConnectionHolder holderToUse = conHolder;
			if (holderToUse == null) {
				holderToUse = new ConnectionHolder(con);
			}
			else {holderToUse.setConnection(con);}
			holderToUse.requested();
			TransactionSynchronizationManager.registerSynchronization(
                              new ConnectionSynchronization(holderToUse, dataSource));
			holderToUse.setSynchronizedWithTransaction(true);
			if (holderToUse != conHolder) {
				TransactionSynchronizationManager.bindResource(
                                                            dataSource, holderToUse);
			}
		}
		return con;
	}
     …
}


    它首先查看当前是否存在事务管理上下文,并尝试从事务管理上下文获取连接,如果获取失败,直接从数据源中获取连接。在获取连接后,如果当前拥有事务上下文,则将连接绑定到事务上下文中。
    我们在JdbcUserService中,使用DataSourceUtils.getConnection()替换直接从数据源中获取连接的代码:
package com.baobaotao.connleak;
…
@Service("jdbcUserService")
public class JdbcUserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void logon(String userName) {
        try {
            //①使用DataSourceUtils获取数据连接
          Connection conn = 
                 DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
            //Connection conn = jdbcTemplate.getDataSource().getConnection();

            String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
            jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
            Thread.sleep(1000); 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


    重新运行代码,得到如下的执行结果:

引用
连接数[active:idle]-[0:0]
连接数[active:idle]-[1:0]
连接数[active:idle]-[0:1]
连接数[active:idle]-[1:0]
连接数[active:idle]-[0:1]


   对照上一节的输出日志,我们可以看到已经没有连接泄漏的现象了。一个执行线程在运行JdbcUserService#logon()方法时,只占用一个连接,而且方法执行完毕后,该连接马上释放。这说明通过DataSourceUtils.getConnection()方法确实获取了方法所在事务上下文绑定的那个连接,而不是像原来那样从数据源中获取一个新的连接。

通过DataSourceUtils获取数据连接

   是否使用DataSourceUtils获取数据连接就可以高枕无忧了呢?理想很美好,但现实很残酷:如果DataSourceUtils在没有事务上下文的方法中使用getConnection()获取连接,依然会造成数据连接泄漏!
   保持上面的代码不变,将上面Spring配置文件中①处的Spring AOP事务增强配置的代码注释掉,重新运行代码清单10-23的代码,将得到如下的输出日志:

引用
连接数[active:idle]-[0:0]
连接数[active:idle]-[1:1]
连接数[active:idle]-[1:1]
连接数[active:idle]-[2:1]
连接数[active:idle]-[2:1]


    我们通过下表对数据源连接的占用和泄漏情况进行描述。
    仔细对上表的执行过程,我们发现在T1时,有事务上下文时的active为2,idle为0,而此时由于没有事务管理,则active为1而idle也为1。这说明有事务上下文时,需要等到整个事务方法(即logon())返回后,事务上下文绑定的连接才被释放。但在没有事务上下文时,logon()调用JdbcTemplate执行完数据操作后,马上就释放连接。

时间 执行线程1 执行线程2 数据源连接
                 active idle leak
T0 未启动 未启动 0 0 0
T1 正在执行方法 未启动 1 1 0
T2 执行完毕 未启动 1 1 1
T3 执行完毕 正式执行方法 2 1 1
T4 执行完毕 执行完毕 2 1 2


   在T2执行线程完成logon()方法的调用后,有一个连接没有被释放(active),所以发生了连接泄漏。到T4时,两个执行线程都完成了logon()方法的调用,但是出现了两个未释放的连接。
   要堵上这个连接泄漏的漏洞,需要对logon()方法进行如下的改造:
package com.baobaotao.connleak;
…
@Service("jdbcUserService")
public class JdbcUserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void logon(String userName) {
        try {
          Connection conn = 
                 DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
            String sql = "UPDATE t_user SET last_logon_time=? WHERE user_name =?";
            jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
            Thread.sleep(1000); 
            //① 
        } catch (Exception e) {
            e.printStackTrace();
        }finally {

            //②显式使用DataSourceUtils释放连接
            DataSourceUtils.releaseConnection(conn,jdbcTemplate.getDataSource());
        }
    }
}

   在②处显式调用DataSourceUtils.releaseConnection()方法释放获取的连接。特别需要指出的是:一定不能在①处释放连接!因为如果logon()在获取连接后,①处代码前这段代码执行时发生异常,则①处释放连接的动作将得不到执行。这将是一个非常具有隐蔽性的连接泄漏的隐患点。

JdbcTemplate如何做到对连接泄漏的免疫

     分析JdbcTemplate的代码,我们可以清楚地看到它开放的每个数据操作方法,首先都使用DataSourceUtils获取连接,在方法返回之前使用DataSourceUtils释放连接。
来看一下JdbcTemplate最核心的一个数据操作方法execute():
public <T> T execute(StatementCallback<T> action) throws DataAccessException {

		//①首先根据DataSourceUtils获取数据连接
         Connection con = DataSourceUtils.getConnection(getDataSource());
		Statement stmt = null;
		try {
			Connection conToUse = con;
			…
			handleWarnings(stmt);
			return result;
		}
		catch (SQLException ex) {
			JdbcUtils.closeStatement(stmt);
			stmt = null;

              //②发生异常时,使用DataSourceUtils释放数据连接
              DataSourceUtils.releaseConnection(con, getDataSource());
			con = null;
			throw getExceptionTranslator().translate(
                                "StatementCallback", getSql(action), ex);
		}
		finally {
			JdbcUtils.closeStatement(stmt);
              
              //③最后再使用DataSourceUtils释放数据连接
			DataSourceUtils.releaseConnection(con, getDataSource());
		}
	}


    在①处通过DataSourceUtils.getConnection()获取连接,在②和③处通过DataSourceUtils.releaseConnection()释放连接。所有JdbcTemplate开放的数据访问API最终都是直接或间接由execute(StatementCallback<T> action)方法执行数据访问操作的,因此这个方法代表了JdbcTemplate数据操作的最终实现方式。
    正是因为JdbcTemplate严谨的获取连接及释放连接的模式化流程保证了JdbcTemplate对数据连接泄漏问题的免疫性。所以,如有可能尽量使用JdbcTemplate、HibernateTemplate等这些模板进行数据访问操作,避免直接获取数据连接的操作。

使用TransactionAwareDataSourceProxy


    如果不得已要显式获取数据连接,除了使用DataSourceUtils获取事务上下文绑定的连接外,还可以通过TransactionAwareDataSourceProxy对数据源进行代理。数据源对象被代理后就具有了事务上下文感知的能力,通过代理数据源的getConnection()方法获取连接和使用DataSourceUtils.getConnection()获取连接的效果是一样的。
   下面是使用TransactionAwareDataSourceProxy对数据源进行代理的配置:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       …
       http://www.springframework.org/schema/tx    
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
    <context:component-scan base-package="com.baobaotao.connleak"/>
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--①未被代理的数据源 -->
    <bean id="originDataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close" 
		p:driverClassName="${jdbc.driverClassName}"
		p:url="${jdbc.url}" 
		p:username="${jdbc.username}"
		p:password="${jdbc.password}"/>
	
     <!--②对数据源进行代码,使数据源具体事务上下文感知性 -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"
		p:targetDataSource-ref="originDataSource" />

    <bean id="jdbcTemplate"
          class="org.springframework.jdbc.core.JdbcTemplate"
          p:dataSource-ref="dataSource"/>

    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
          p:dataSource-ref="dataSource"/>         
    <tx:annotation-driven/>      
</beans>


    对数据源进行代理后,我们就可以通过数据源代理对象的getConnection()获取事务上下文中绑定的数据连接了。因此,如果数据源已经进行了   TransactionAwareDataSourceProxy的代理,而且方法存在事务上下文,那么代码清单10-19的代码也不会生产连接泄漏的问题。

其他数据访问技术的等价类

    理解了Spring JDBC的数据连接泄漏问题,其中的道理可以平滑地推广到其他框架中去。Spring为每个数据访问技术框架都提供了一个获取事务上下文绑定的数据连接(或其衍生品)的工具类和数据源(或其衍生品)的代理类。
   表10-5列出了不同数据访问技术对应DataSourceUtils的等价类。
表10-5  不同数据访问框架DataSourceUtils的等价类
数据访问技术框架 连接(或衍生品)获取工具类                               
Spring JDBC org.springframework.jdbc.datasource.DataSourceUtils      
Hibernate org.springframework.orm.hibernate3.SessionFactoryUtils   
iBatis org.springframework.jdbc.datasource.DataSourceUtils      
JPA org.springframework.orm.jpa.EntityManagerFactoryUtils    
JDO org.springframework.orm.jdo.PersistenceManagerFactoryUtils


  表10-6列出了不同数据访问技术框架下TransactionAwareDataSourceProxy的等价类。
表10-6  不同数据访问框架TransactionAwareDataSourceProxy的等价类
数据访问技术框架 连接(或衍生品)获取工具类                                                    
Spring JDBC org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy       
Hibernate org.springframework.orm.hibernate3.LocalSessionFactoryBean                    
iBatis org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy           
JPA 无                                                                        
JDO org.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy


注:以上内容摘自《Spring 4.x企业应用开发实战》
分享到:
评论
2 楼 huang_yong 2012-04-14  
写程序的时候,如果是从底层获取Connection,一定要注意在finally里面close掉,但是如果从Hibernate的SessionFactory级别获取Connection,就无需手工close掉,这些工作都由Hibernate为我们完成了。

一定要使用:

Connection conn = SessionFactoryUtils.getDataSource(hibernateTemplate.getSessionFactory()).getConnection();

获取Connection,以上代码可以进行封装,放入公共类中,方便其他程序员使用。
1 楼 jinnianshilongnian 2012-03-09  
1、打开连接不关闭 这是程序员犯的不该犯的错误(发现错误后可以改)

2、当有事务方法 特别慢,会拖慢整个应用 甚至造成死锁。
    以前我们用c3p0曾经遇到过类似的,在用户注册高峰时,由于赠送积分/还有一些道具之类的是和另一个系统集成的,所以在高峰期特别慢,从而导致整个注册方法特别慢,最后改成异步赠送(失败了影响也是比较小的)。

因此第一种情况,是可以查找并改正的。
第二种情况,需要实际情况实际分析。

相关推荐

    Spring.3.x企业应用开发实战(完整版).part2

    第10章 Spring的事务管理难点剖析 10.1 DAO和事务管理的牵绊 10.1.1 JDBC访问数据库 10.1.2 Hibernate访问数据库 10.2 应用分层的迷惑 10.3 事务方法嵌套调用的迷茫 10.3.1 Spring事务传播机制回顾 10.3.2 相互嵌套...

    Spring3.x企业应用开发实战(完整版) part1

    第10章 Spring的事务管理难点剖析 10.1 DAO和事务管理的牵绊 10.1.1 JDBC访问数据库 10.1.2 Hibernate访问数据库 10.2 应用分层的迷惑 10.3 事务方法嵌套调用的迷茫 10.3.1 Spring事务传播机制回顾 10.3.2 相互嵌套...

    基于jsp的新闻管理系统源码数据库.doc

    - **解决方案**:利用事务管理机制确保数据的一致性,通过数据库约束保证数据完整性。 - **难点二**:如何提高系统的并发处理能力? - **解决方案**:优化查询语句,使用缓存技术减少数据库访问频率,合理配置...

    学生成绩管理系统

    学生成绩管理系统的核心目标是自动化处理与成绩相关的事务,包括录入、查询、统计和分析学生分数。它减少了手动操作的繁琐,提高了工作效率,同时也确保了数据的准确性和一致性。系统通常包含以下主要模块: 1. ...

    基于springboot+Javaweb的药店管理系统源码数据库.doc

    - **数据一致性**:通过事务管理和数据库锁机制保证数据的一致性和完整性。 - **性能优化**:采用缓存技术和异步处理等方式提高系统响应速度。 - **安全性**:采用SSL加密传输、SQL注入预防等措施加强系统安全性。 ...

    酒店管理系统的设计与实现.zip

    8. **表格**:“表.docx”可能包含系统的数据统计表格、需求分析表、任务分配表等,帮助管理和组织项目进程。 总的来说,《酒店管理系统的设计与实现》这个项目不仅展示了软件开发的全生命周期,还涵盖了多种IT技术...

    SSM教师档案管理系统的设计与实现毕业论文.docx

    SSH(Spring、Struts2、Hibernate)是一个常见的Java Web开发框架,它提供了模型、视图和控制器的分离,以及数据持久化、事务管理等功能。 3.2 JSP JSP(JavaServer Pages)用于动态生成网页,结合Servlet技术,...

    115个Java面试要点.zip

    11. **数据库知识**:SQL基本操作,事务管理,JDBC,连接池(C3P0, HikariCP等),以及NoSQL数据库如MongoDB的基本概念。 12. **网络编程**:TCP/IP协议基础,Socket编程,HTTP协议,RESTful API设计。 13. **并发...

    南京面试题 Accp 5.0 S2

    7. **软件框架和工具**:根据ACCP课程,可能会考察Java相关的Spring框架、Maven构建工具,或者前端的React、Vue.js等。面试者需要展示对这些工具的熟练使用和理解。 8. **问题解决能力**:面试官可能会给出一些实际...

    全网最齐全的Java面试题库-附答案.zip

    面试中可能会要求分析内存泄漏或性能瓶颈,或者让你解释各种GC算法的工作原理。 Spring框架作为Java企业级应用的主流选择,其核心概念如依赖注入(DI)、AOP(面向切面编程)、Bean生命周期管理等都是面试热点。...

    【Java面试资料】-互联网大厂面试题库大全

    数据库操作是大多数互联网应用的基础,因此SQL语句的编写和优化、事务管理、索引原理、数据库设计原则等内容也是面试中不可或缺的部分。尤其在大数据量的背景下,如何进行高效的查询和存储是考察的重点。 框架和...

    java程序员笔试面试大全(附全部试题答案)

    理解堆栈内存的区别,知道如何进行对象引用,以及分析内存泄漏和性能优化,这些都是面试中可能被问到的问题。 Java多线程是另一个难点,包括线程的创建、同步机制(synchronized、Lock、Semaphore等)、线程池的...

Global site tag (gtag.js) - Google Analytics