Java面试题下篇(转)
这部分主要是开源Java EE框架方面的内容,包括Hibernate、MyBatis、Spring、Spring MVC等,由于Struts 2已经是明日黄花,在这里就不讨论Struts 2的面试题,如果需要了解相关内容,可以参考我的另一篇文章《Java面试题集(86-115)》。此外,这篇文章还对企业应用架构、大型网站架构和应用服务器优化等内容进行了简单的探讨,这些内容相信对面试会很有帮助。
说明:对于load()方法Hibernate认为该数据在数据库中一定存在可以放心的使用代理来实现延迟加载,如果没有数据就抛出异常,而通过get()方法获取的数据可以不存在。
说明:关于N+1查询问题,可以参考CSDN上的一篇文章《什么是N+1查询》
提示:使用乐观锁会增加了一个版本字段,很明显这需要额外的空间来存储这个版本字段,浪费了空间,但是乐观锁会让系统具有更好的并发性,这是对时间的节省。因此乐观锁也是典型的空间换时间的策略。
- 瞬时态:当new一个实体对象后,这个对象处于瞬时态,即这个对象只是一个保存临时数据的内存区域,如果没有变量引用这个对象,则会被JVM的垃圾回收机制回收。这个对象所保存的数据与数据库没有任何关系,除非通过Session的save()、saveOrUpdate()、persist()、merge()方法把瞬时态对象与数据库关联,并把数据插入或者更新到数据库,这个对象才转换为持久态对象。
- 持久态:持久态对象的实例在数据库中有对应的记录,并拥有一个持久化标识(ID)。对持久态对象进行delete操作后,数据库中对应的记录将被删除,那么持久态对象与数据库记录不再存在对应关系,持久态对象变成移除态(可以视为瞬时态)。持久态对象被修改变更后,不会马上同步到数据库,直到数据库事务提交。
- 游离态:当Session进行了close()、clear()、evict()或flush()后,实体对象从持久态变成游离态,对象虽然拥有持久和与数据库对应记录一致的标识值,但是因为对象已经从会话中清除掉,对象不在持久化管理之内,所以处于游离态(也叫脱管态)。游离态的对象与临时状态对象是十分相似的,只是它还含有持久化标识。
提示:关于这个问题,在Hibernate的官方文档中有更为详细的解读。
#
和$
书写占位符有什么区别? #
将传入的数据都当成一个字符串,会对传入的数据自动加上引号;$
将传入的数据直接显示生成在SQL中。注意:使用$
占位符可能会导致SQL注射攻击,能用#
的地方就不要使用$
,写order by子句的时候应该用$
而不是#
。下面是映射文件的片段。
<select id="foo" parameterType="Blog" resultType="Blog">
select * from t_blog where 1 = 1
<if test="title != null">
and title = #{title}
</if>
<if test="content != null">
and content = #{content}
</if>
<if test="owner != null">
and owner = #{owner}
</if>
</select>
当然也可以像下面这些书写。
<select id="foo" parameterType="Blog" resultType="Blog">
select * from t_blog where 1 = 1
<choose>
<when test="title != null">
and title = #{title}
</when>
<when test="content != null">
and content = #{content}
</when>
<otherwise>
and owner = "owner1"
</otherwise>
</choose>
</select>
再看看下面这个例子。
<select id="bar" resultType="Blog">
select * from t_blog where id in
<foreach collection="array" index="index"
item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
举个例子:一个类A需要用到接口B中的方法,那么就需要为类A和接口B建立关联或依赖关系,最原始的方法是在类A中创建一个接口B的实现类C的实例,但这种方法需要开发人员自行维护二者的依赖关系,也就是说当依赖关系发生变动的时候需要修改代码并重新构建整个系统。如果通过一个容器来管理这些对象以及对象的依赖关系,则只需要在类A中定义好用于关联接口B的方法(构造器或setter方法),将类A和接口B的实现类C放入容器中,通过对容器的配置来实现二者的关联。
依赖注入可以通过setter方法注入(设值注入)、构造器注入和接口注入三种方式来实现,Spring支持setter注入和构造器注入,通常使用构造器注入来注入必须的依赖关系,对于可选的依赖关系,则setter注入是更好的选择,setter注入需要类提供无参构造器或者无参的静态工厂方法来创建对象。
补充:设计模式中的创建型模式中也有一个原型模式,原型模式也是一个常用的模式,例如做一个室内设计软件,所有的素材都在工具箱中,而每次从工具箱中取出的都是素材对象的一个原型,可以通过对象克隆来实现原型模式。
Spring 2.x中针对WebApplicationContext新增了3个作用域,分别是:request(每次HTTP请求都会创建一个新的Bean)、session(同一个HttpSession共享同一个Bean,不同的HttpSession使用不同的Bean)和globalSession(同一个全局Session共享一个Bean)。
说明:单例模式和原型模式都是重要的设计模式。一般情况下,无状态或状态不可变的类适合使用单例模式。在传统开发中,由于DAO持有Connection这个非线程安全对象因而没有使用单例模式;但在Spring环境下,所有DAO类对可以采用单例模式,因为Spring利用AOP和Java API中的ThreadLocal对非线程安全的对象进行了特殊处理。
ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。ThreadLocal,顾名思义是线程的一个本地化对象,当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本,所以每一个线程都可以独立的改变自己的副本,而不影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量。
ThreadLocal是如何做到为每一个线程维护一份独立的变量副本的呢?在ThreadLocal类中有一个Map,键为线程对象,值是其线程对应的变量的副本,自己要模拟实现一个ThreadLocal类其实并不困难,代码如下所示:
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class MyThreadLocal<T> {
private Map<Thread, T> map = Collections.synchronizedMap(new HashMap<Thread, T>());
public void set(T newValue) {
map.put(Thread.currentThread(), newValue);
}
public T get() {
return map.get(Thread.currentThread());
}
public void remove() {
map.remove(Thread.currentThread());
}
}
说明: Advice在国内的很多书面资料中都被翻译成"通知",但是很显然这个翻译无法表达其本质,有少量的读物上将这个词翻译为"增强",这个翻译是对Advice较为准确的诠释,我们通过AOP将横切关注功能加到原有的业务逻辑上,这就是对原有业务逻辑的一种增强,这种增强可以是前置增强、后置增强、返回后增强、抛异常时增强和包围型增强。
补充:代理模式是GoF提出的23种设计模式中最为经典的模式之一,代理模式是对象的结构模式,它给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。简单的说,代理对象可以完成比原对象更多的职责,当需要为原对象添加横切关注功能时,就可以使用原对象的代理对象。我们在打开Office系列的Word文档时,如果文档中有插图,当文档刚加载时,文档中的插图都只是一个虚框占位符,等用户真正翻到某页要查看该图片时,才会真正加载这张图,这其实就是对代理模式的使用,代替真正图片的虚框就是一个虚拟代理;Hibernate的load方法也是返回一个虚拟代理对象,等用户真正需要访问对象的属性时,才向数据库发出SQL语句获得真实对象。
下面用一个找枪手代考的例子演示代理模式的使用:
/**
* 参考人员接口
* @author 骆昊
*
*/
public interface Candidate {
/**
* 答题
*/
public void answerTheQuestions();
}
/**
* 懒学生
* @author 骆昊
*
*/
public class LazyStudent implements Candidate {
private String name; // 姓名
public LazyStudent(String name) {
this.name = name;
}
@Override
public void answerTheQuestions() {
// 懒学生只能写出自己的名字不会答题
System.out.println("姓名: " + name);
}
}
/**
* 枪手
* @author 骆昊
*
*/
public class Gunman implements Candidate {
private Candidate target; // 被代理对象
public Gunman(Candidate target) {
this.target = target;
}
@Override
public void answerTheQuestions() {
// 枪手要写上代考的学生的姓名
target.answerTheQuestions();
// 枪手要帮助懒学生答题并交卷
System.out.println("奋笔疾书正确答案");
System.out.println("交卷");
}
}
public class ProxyTest1 {
public static void main(String[] args) {
Candidate c = new Gunman(new LazyStudent("王小二"));
c.answerTheQuestions();
}
}
说明:从JDK 1.3开始,Java提供了动态代理技术,允许开发者在运行时创建接口的代理实例,主要包括Proxy类和InvocationHandler接口。下面的例子使用动态代理为ArrayList编写一个代理,在添加和删除元素时,在控制台打印添加或删除的元素以及ArrayList的大小:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
public class ListProxy<T> implements InvocationHandler {
private List<T> target;
public ListProxy(List<T> target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object retVal = null;
System.out.println("[" + method.getName() + ": " + args[0] + "]");
retVal = method.invoke(target, args);
System.out.println("[size=" + target.size() + "]");
return retVal;
}
}
说明:使用Java的动态代理有一个局限性就是代理的类必须要实现接口,虽然面向接口编程是每个优秀的Java程序都知道的规则,但现实往往不尽如人意,对于没有实现接口的类如何为其生成代理呢?继承!继承是最经典的扩展已有代码能力的手段,虽然继承常常被初学者滥用,但继承也常常被进阶的程序员忽视。CGLib采用非常底层的字节码生成技术,通过为一个类创建子类来生成代理,它弥补了Java动态代理的不足,因此Spring中动态代理和CGLib都是创建代理的重要手段,对于实现了接口的类就用动态代理为其生成代理类,而没有实现接口的类就用CGLib通过继承的方式为其创建代理。
说明:自动装配没有自定义装配方式那么精确,而且不能自动装配简单属性(基本类型、字符串等),在使用时应注意。
<context:component-scan base-package="org.example"/>
然后可以用@Component、@Controller、@Service、@Repository注解来标注需要由Spring IoC容器进行对象托管的类。这几个注解没有本质区别,只不过@Controller通常用于控制器,@Service通常用于业务逻辑类,@Repository通常用于仓储类(例如我们的DAO实现类),普通的类用@Component来标注。
事务分为全局事务和局部事务。全局事务由应用服务器管理,需要底层服务器JTA支持(如WebLogic、WildFly等)。局部事务和底层采用的持久化方案有关,例如使用JDBC进行持久化时,需要使用Connetion对象来操作事务;而采用Hibernate进行持久化时,需要使用Session对象来操作事务。
Spring提供了如下所示的事务管理器。
事务管理器实现类 | 目标对象 |
---|---|
DataSourceTransactionManager | 注入DataSource |
HibernateTransactionManager | 注入SessionFactory |
JdoTransactionManager | 管理JDO事务 |
JtaTransactionManager | 使用JTA管理事务 |
PersistenceBrokerTransactionManager | 管理Apache的OJB事务 |
这些事务的父接口都是PlatformTransactionManager。Spring的事务管理机制是一种典型的策略模式,PlatformTransactionManager代表事务管理接口,该接口定义了三个方法,该接口并不知道底层如何管理事务,但是它的实现类必须提供getTransaction()方法(开启事务)、commit()方法(提交事务)、rollback()方法(回滚事务)的多态实现,这样就可以用不同的实现类代表不同的事务管理策略。使用JTA全局事务策略时,需要底层应用服务器支持,而不同的应用服务器所提供的JTA全局事务可能存在细节上的差异,因此实际配置全局事务管理器是可能需要使用JtaTransactionManager的子类,如:WebLogicJtaTransactionManager(Oracle的WebLogic服务器提供)、UowJtaTransactionManager(IBM的WebSphere服务器提供)等。
编程式事务管理如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:p="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.jackfrued"/>
<bean id="propertyConfig"
class="org.springframework.beans.factory.config.
PropertyPlaceholderConfigurer">
<property name="location">
<value>jdbc.properties</value>
</property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>${db.driver}</value>
</property>
<property name="url">
<value>${db.url}</value>
</property>
<property name="username">
<value>${db.username}</value>
</property>
<property name="password">
<value>${db.password}</value>
</property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
<!-- JDBC事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.
DataSourceTransactionManager" scope="singleton">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
<!-- 声明事务模板 -->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.
TransactionTemplate">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
</bean>
</beans>
package com.jackfrued.dao.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import com.jackfrued.dao.EmpDao;
import com.jackfrued.entity.Emp;
@Repository
public class EmpDaoImpl implements EmpDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public boolean save(Emp emp) {
String sql = "insert into emp values (?,?,?)";
return jdbcTemplate.update(sql, emp.getId(), emp.getName(), emp.getBirthday()) == 1;
}
}
package com.jackfrued.biz.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.jackfrued.biz.EmpService;
import com.jackfrued.dao.EmpDao;
import com.jackfrued.entity.Emp;
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private TransactionTemplate txTemplate;
@Autowired
private EmpDao empDao;
@Override
public void addEmp(final Emp emp) {
txTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus txStatus) {
empDao.save(emp);
}
});
}
}
声明式事务如下图所示,以Spring整合Hibernate 3为例,包括完整的DAO和业务逻辑代码。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
<!-- 配置由Spring IoC容器托管的对象对应的被注解的类所在的包 -->
<context:component-scan base-package="com.jackfrued" />
<!-- 配置通过自动生成代理实现AOP功能 -->
<aop:aspectj-autoproxy />
<!-- 配置数据库连接池 (DBCP) -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<!-- 配置驱动程序类 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<!-- 配置连接数据库的URL -->
<property name="url" value="jdbc:mysql://localhost:3306/myweb" />
<!-- 配置访问数据库的用户名 -->
<property name="username" value="root" />
<!-- 配置访问数据库的口令 -->
<property name="password" value="123456" />
<!-- 配置最大连接数 -->
<property name="maxActive" value="150" />
<!-- 配置最小空闲连接数 -->
<property name="minIdle" value="5" />
<!-- 配置最大空闲连接数 -->
<property name="maxIdle" value="20" />
<!-- 配置初始连接数 -->
<property name="initialSize" value="10" />
<!-- 配置连接被泄露时是否生成日志 -->
<property name="logAbandoned" value="true" />
<!-- 配置是否删除超时连接 -->
<property name="removeAbandoned" value="true" />
<!-- 配置删除超时连接的超时门限值(以秒为单位) -->
<property name="removeAbandonedTimeout" value="120" />
<!-- 配置超时等待时间(以毫秒为单位) -->
<property name="maxWait" value="5000" />
<!-- 配置空闲连接回收器线程运行的时间间隔(以毫秒为单位) -->
<property name="timeBetweenEvictionRunsMillis" value="300000" />
<!-- 配置连接空闲多长时间后(以毫秒为单位)被断开连接 -->
<property name="minEvictableIdleTimeMillis" value="60000" />
</bean>
<!-- 配置Spring提供的支持注解ORM映射的Hibernate会话工厂 -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<!-- 通过setter注入数据源属性 -->
<property name="dataSource" ref="dataSource" />
<!-- 配置实体类所在的包 -->
<property name="packagesToScan" value="com.jackfrued.entity" />
<!-- 配置Hibernate的相关属性 -->
<property name="hibernateProperties">
<!-- 在项目调试完成后要删除show_sql和format_sql属性否则对性能有显著影响 -->
<value>
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
</value>
</property>
</bean>
<!-- 配置Spring提供的Hibernate事务管理器 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<!-- 通过setter注入Hibernate会话工厂 -->
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 配置基于注解配置声明式事务 -->
<tx:annotation-driven />
</beans>
package com.jackfrued.dao;
import java.io.Serializable;
import java.util.List;
import com.jackfrued.comm.QueryBean;
import com.jackfrued.comm.QueryResult;
/**
* 数据访问对象接口(以对象为单位封装CRUD操作)
* @author 骆昊
*
* @param <E> 实体类型
* @param <K> 实体标识字段的类型
*/
public interface BaseDao <E, K extends Serializable> {
/**
* 新增
* @param entity 业务实体对象
* @return 增加成功返回实体对象的标识
*/
public K save(E entity);
/**
* 删除
* @param entity 业务实体对象
*/
public void delete(E entity);
/**
* 根据ID删除
* @param id 业务实体对象的标识
* @return 删除成功返回true否则返回false
*/
public boolean deleteById(K id);
/**
* 修改
* @param entity 业务实体对象
* @return 修改成功返回true否则返回false
*/
public void update(E entity);
/**
* 根据ID查找业务实体对象
* @param id 业务实体对象的标识
* @return 业务实体对象对象或null
*/
public E findById(K id);
/**
* 根据ID查找业务实体对象
* @param id 业务实体对象的标识
* @param lazy 是否使用延迟加载
* @return 业务实体对象对象
*/
public E findById(K id, boolean lazy);
/**
* 查找所有业务实体对象
* @return 装所有业务实体对象的列表容器
*/
public List<E> findAll();
/**
* 分页查找业务实体对象
* @param page 页码
* @param size 页面大小
* @return 查询结果对象
*/
public QueryResult<E> findByPage(int page, int size);
/**
* 分页查找业务实体对象
* @param queryBean 查询条件对象
* @param page 页码
* @param size 页面大小
* @return 查询结果对象
*/
public QueryResult<E> findByPage(QueryBean queryBean, int page, int size);
}
package com.jackfrued.dao;
import java.io.Serializable;
import java.util.List;
import com.jackfrued.comm.QueryBean;
import com.jackfrued.comm.QueryResult;
/**
* BaseDao的缺省适配器
* @author 骆昊
*
* @param <E> 实体类型
* @param <K> 实体标识字段的类型
*/
public abstract class BaseDaoAdapter<E, K extends Serializable> implements
BaseDao<E, K> {
@Override
public K save(E entity) {
return null;
}
@Override
public void delete(E entity) {
}
@Override
public boolean deleteById(K id) {
E entity = findById(id);
if(entity != null) {
delete(entity);
return true;
}
return false;
}
@Override
public void update(E entity) {
}
@Override
public E findById(K id) {
return null;
}
@Override
public E findById(K id, boolean lazy) {
return null;
}
@Override
public List<E> findAll() {
return null;
}
@Override
public QueryResult<E> findByPage(int page, int size) {
return null;
}
@Override
public QueryResult<E> findByPage(QueryBean queryBean, int page, int size) {
return null;
}
}
package com.jackfrued.dao;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.jackfrued.comm.HQLQueryBean;
import com.jackfrued.comm.QueryBean;
import com.jackfrued.comm.QueryResult;
/**
* 基于Hibernate的BaseDao实现类
* @author 骆昊
*
* @param <E> 实体类型
* @param <K> 主键类型
*/
@SuppressWarnings(value = {"unchecked"})
public abstract class BaseDaoHibernateImpl<E, K extends Serializable> extends BaseDaoAdapter<E, K> {
@Autowired
protected SessionFactory sessionFactory;
private Class<?> entityClass; // 业务实体的类对象
private String entityName; // 业务实体的名字
public BaseDaoHibernateImpl() {
ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
entityClass = (Class<?>) pt.getActualTypeArguments()[0];
entityName = entityClass.getSimpleName();
}
@Override
public K save(E entity) {
return (K) sessionFactory.getCurrentSession().save(entity);
}
@Override
public void delete(E entity) {
sessionFactory.getCurrentSession().delete(entity);
}
@Override
public void update(E entity) {
sessionFactory.getCurrentSession().update(entity);
}
@Override
public E findById(K id) {
return findById(id, false);
}
@Override
public E findById(K id, boolean lazy) {
Session session = sessionFactory.getCurrentSession();
return (E) (lazy? session.load(entityClass, id) : session.get(entityClass, id));
}
@Override
public List<E> findAll() {
return sessionFactory.getCurrentSession().createCriteria(entityClass).list();
}
@Override
public QueryResult<E> findByPage(int page, int size) {
return new QueryResult<E>(
findByHQLAndPage("from " + entityName , page, size),
getCountByHQL("select count(*) from " + entityName)
);
}
@Override
public QueryResult<E> findByPage(QueryBean queryBean, int page, int size) {
if(queryBean instanceof HQLQueryBean) {
HQLQueryBean hqlQueryBean = (HQLQueryBean) queryBean;
return new QueryResult<E>(
findByHQLAndPage(hqlQueryBean.getQueryString(), page, size, hqlQueryBean.getParameters()),
getCountByHQL(hqlQueryBean.getCountString(), hqlQueryBean.getParameters())
);
}
return null;
}
/**
* 根据HQL和可变参数列表进行查询
* @param hql 基于HQL的查询语句
* @param params 可变参数列表
* @return 持有查询结果的列表容器或空列表容器
*/
protected List<E> findByHQL(String hql, Object... params) {
return this.findByHQL(hql, getParamList(params));
}
/**
* 根据HQL和参数列表进行查询
* @param hql 基于HQL的查询语句
* @param params 查询参数列表
* @return 持有查询结果的列表容器或空列表容器
*/
protected List<E> findByHQL(String hql, List<Object> params) {
List<E> list = createQuery(hql, params).list();
return list != null && list.size() > 0 ? list : Collections.EMPTY_LIST;
}
/**
* 根据HQL和参数列表进行分页查询
* @param hql 基于HQL的查询语句
* @page 页码
* @size 页面大小
* @param params 可变参数列表
* @return 持有查询结果的列表容器或空列表容器
*/
protected List<E> findByHQLAndPage(String hql, int page, int size, Object... params) {
return this.findByHQLAndPage(hql, page, size, getParamList(params));
}
/**
* 根据HQL和参数列表进行分页查询
* @param hql 基于HQL的查询语句
* @page 页码
* @size 页面大小
* @param params 查询参数列表
* @return 持有查询结果的列表容器或空列表容器
*/
protected List<E> findByHQLAndPage(String hql, int page, int size, List<Object> params) {
List<E> list = createQuery(hql, params)
.setFirstResult((page - 1) * size)
.setMaxResults(size)
.list();
return list != null && list.size() > 0 ? list : Collections.EMPTY_LIST;
}
/**
* 查询满足条件的记录数
* @param hql 基于HQL的查询语句
* @param params 可变参数列表
* @return 满足查询条件的总记录数
*/
protected long getCountByHQL(String hql, Object... params) {
return this.getCountByHQL(hql, getParamList(params));
}
/**
* 查询满足条件的记录数
* @param hql 基于HQL的查询语句
* @param params 参数列表容器
* @return 满足查询条件的总记录数
*/
protected long getCountByHQL(String hql, List<Object> params) {
return (Long) createQuery(hql, params).uniqueResult();
}
// 创建Hibernate查询对象(Query)
private Query createQuery(String hql, List<Object> params) {
Query query = sessionFactory.getCurrentSession().createQuery(hql);
for(int i = 0; i < params.size(); i++) {
query.setParameter(i, params.get(i));
}
return query;
}
// 将可变参数列表组装成列表容器
private List<Object> getParamList(Object... params) {
List<Object> paramList = new ArrayList<>();
if(params != null) {
for(int i = 0; i < params.length; i++) {
paramList.add(params[i]);
}
}
return paramList.size() == 0? Collections.EMPTY_LIST : paramList;
}
}
package com.jackfrued.comm;
import java.util.List;
/**
* 查询条件的接口
* @author 骆昊
*
*/
public interface QueryBean {
/**
* 添加排序字段
* @param fieldName 用于排序的字段
* @param asc 升序还是降序
* @return 查询条件对象自身(方便级联编程)
*/
public QueryBean addOrder(String fieldName, boolean asc);
/**
* 添加排序字段
* @param available 是否添加此排序字段
* @param fieldName 用于排序的字段
* @param asc 升序还是降序
* @return 查询条件对象自身(方便级联编程)
*/
public QueryBean addOrder(boolean available, String fieldName, boolean asc);
/**
* 添加查询条件
* @param condition 条件
* @param params 替换掉条件中参数占位符的参数
* @return 查询条件对象自身(方便级联编程)
*/
public QueryBean addCondition(String condition, Object... params);
/**
* 添加查询条件
* @param available 是否需要添加此条件
* @param condition 条件
* @param params 替换掉条件中参数占位符的参数
* @return 查询条件对象自身(方便级联编程)
*/
public QueryBean addCondition(boolean available, String condition, Object... params);
/**
* 获得查询语句
* @return 查询语句
*/
public String getQueryString();
/**
* 获取查询记录数的查询语句
* @return 查询记录数的查询语句
*/
public String getCountString();
/**
* 获得查询参数
* @return 查询参数的列表容器
*/
public List<Object> getParameters();
}
package com.jackfrued.comm;
import java.util.List;
/**
* 查询结果
* @author 骆昊
*
* @param <T> 泛型参数
*/
public class QueryResult<T> {
private List<T> result; // 持有查询结果的列表容器
private long totalRecords; // 查询到的总记录数
/**
* 构造器
*/
public QueryResult() {
}
/**
* 构造器
* @param result 持有查询结果的列表容器
* @param totalRecords 查询到的总记录数
*/
public QueryResult(List<T> result, long totalRecords) {
this.result = result;
this.totalRecords = totalRecords;
}
public List<T> getResult() {
return result;
}
public void setResult(List<T> result) {
this.result = result;
}
public long getTotalRecords() {
return totalRecords;
}
public void setTotalRecords(long totalRecords) {
this.totalRecords = totalRecords;
}
}
package com.jackfrued.dao;
import com.jackfrued.comm.QueryResult;
import com.jackfrued.entity.Dept;
/**
* 部门数据访问对象接口
* @author 骆昊
*
*/
public interface DeptDao extends BaseDao<Dept, Integer> {
/**
* 分页查询*部门
* @param page 页码
* @param size 页码大小
* @return 查询结果对象
*/
public QueryResult<Dept> findTopDeptByPage(int page, int size);
}
package com.jackfrued.dao.impl;
import java.util.List;
import org.springframework.stereotype.Repository;
import com.jackfrued.comm.QueryResult;
import com.jackfrued.dao.BaseDaoHibernateImpl;
import com.jackfrued.dao.DeptDao;
import com.jackfrued.entity.Dept;
@Repository
public class DeptDaoImpl extends BaseDaoHibernateImpl<Dept, Integer> implements DeptDao {
private static final String HQL_FIND_TOP_DEPT = " from Dept as d where d.superiorDept is null ";
@Override
public QueryResult<Dept> findTopDeptByPage(int page, int size) {
List<Dept> list = findByHQLAndPage(HQL_FIND_TOP_DEPT, page, size);
long totalRecords = getCountByHQL(" select count(*) " + HQL_FIND_TOP_DEPT);
return new QueryResult<>(list, totalRecords);
}
}
package com.jackfrued.comm;
import java.util.List;
/**
* 分页器
* @author 骆昊
*
* @param <T> 分页数据对象的类型
*/
public class PageBean<T> {
private static final int DEFAUL_INIT_PAGE = 1;
private static final int DEFAULT_PAGE_SIZE = 10;
private static final int DEFAULT_PAGE_COUNT = 5;
private List<T> data; // 分页数据
private PageRange pageRange; // 页码范围
private int totalPage; // 总页数
private int size; // 页面大小
private int currentPage; // 当前页码
private int pageCount; // 页码数量
/**
* 构造器
* @param currentPage 当前页码
* @param size 页码大小
* @param pageCount 页码数量
*/
public PageBean(int currentPage, int size, int pageCount) {
this.currentPage = currentPage > 0 ? currentPage : 1;
this.size = size > 0 ? size : DEFAULT_PAGE_SIZE;
this.pageCount = pageCount > 0 ? size : DEFAULT_PAGE_COUNT;
}
/**
* 构造器
* @param currentPage 当前页码
* @param size 页码大小
*/
public PageBean(int currentPage, int size) {
this(currentPage, size, DEFAULT_PAGE_COUNT);
}
/**
* 构造器
* @param currentPage 当前页码
*/
public PageBean(int currentPage) {
this(currentPage, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT);
}
/**
* 构造器
*/
public PageBean() {
this(DEFAUL_INIT_PAGE, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT);
}
public List<T> getData() {
return data;
}
public int getStartPage() {
return pageRange != null ? pageRange.getStartPage() : 1;
}
public int getEndPage() {
return pageRange != null ? pageRange.getEndPage() : 1;
}
public long getTotalPage() {
return totalPage;
}
public int getSize() {
return size;
}
public int getCurrentPage() {
return currentPage;
}
/**
* 将查询结果转换为分页数据
* @param queryResult 查询结果对象
*/
public void transferQueryResult(QueryResult<T> queryResult) {
long totalRecords = queryResult.getTotalRecords();
data = queryResult.getResult();
totalPage = (int) ((totalRecords + size - 1) / size);
totalPage = totalPage >= 0 ? totalPage : Integer.MAX_VALUE;
this.pageRange = new PageRange(pageCount, currentPage, totalPage);
}
}
package com.jackfrued.comm;
/**
* 页码范围
* @author 骆昊
*
*/
public class PageRange {
private int startPage; // 起始页码
private int endPage; // 终止页码
/**
* 构造器
* @param pageCount 总共显示几个页码
* @param currentPage 当前页码
* @param totalPage 总页数
*/
public PageRange(int pageCount, int currentPage, int totalPage) {
startPage = currentPage - (pageCount - 1) / 2;
endPage = currentPage + pageCount / 2;
if(startPage < 1) {
startPage = 1;
endPage = totalPage > pageCount ? pageCount : totalPage;
}
if (endPage > totalPage) {
endPage = totalPage;
startPage = (endPage - pageCount > 0) ? endPage - pageCount + 1 : 1;
}
}
/**
* 获得起始页页码
* @return 起始页页码
*/
public int getStartPage() {
return startPage;
}
/**
* 获得终止页页码
* @return 终止页页码
*/
public int getEndPage() {
return endPage;
}
}
package com.jackfrued.biz;
import com.jackfrued.comm.PageBean;
import com.jackfrued.entity.Dept;
/**
* 部门业务逻辑接口
* @author 骆昊
*
*/
public interface DeptService {
/**
* 创建新的部门
* @param department 部门对象
* @return 创建成功返回true否则返回false
*/
public boolean createNewDepartment(Dept department);
/**
* 删除指定部门
* @param id 要删除的部门的编号
* @return 删除成功返回true否则返回false
*/
public boolean deleteDepartment(Integer id);
/**
* 分页获取*部门
* @param page 页码
* @param size 页码大小
* @return 部门对象的分页器对象
*/
public PageBean<Dept> getTopDeptByPage(int page, int size);
}
package com.jackfrued.biz.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.jackfrued.biz.DeptService;
import com.jackfrued.comm.PageBean;
import com.jackfrued.comm.QueryResult;
import com.jackfrued.dao.DeptDao;
import com.jackfrued.entity.Dept;
@Service
@Transactional // 声明式事务的注解
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptDao deptDao;
@Override
public boolean createNewDepartment(Dept department) {
return deptDao.save(department) != null;
}
@Override
public boolean deleteDepartment(Integer id) {
return deptDao.deleteById(id);
}
@Override
public PageBean<Dept> getTopDeptByPage(int page, int size) {
QueryResult<Dept> queryResult = deptDao.findTopDeptByPage(page, size);
PageBean<Dept> pageBean = new PageBean<>(page, size);
pageBean.transferQueryResult(queryResult);
return pageBean;
}
}
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<web-app>
<servlet>
<servlet-name>example</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>example</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</web-app>
说明:上面的配置中使用了*.html的后缀映射,这样做一方面不能够通过URL推断采用了何种服务器端的技术,另一方面可以欺骗搜索引擎,因为搜索引擎不会搜索动态页面,这种做法称为伪静态化。
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
C3P0配置:
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
提示: DBCP的详细配置在第153题中已经完整的展示过了。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- this is the service object that we want to make transactional -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- the transactional advice -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- ensure that the above transactional advice runs for any execution
of an operation defined by the FooService interface -->
<aop:config>
<aop:pointcut id="fooServiceOperation"
expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<!-- don't forget the DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<!-- similarly, don't forget the PlatformTransactionManager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- other <bean/> definitions here -->
</beans>
package com.jackfrued.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Person {
private String name;
private int age;
@Autowired
private Car car;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
}
}
package com.jackfrued.bean;
import org.springframework.stereotype.Component;
@Component
public class Car {
private String brand;
private int maxSpeed;
public Car(String brand, int maxSpeed) {
this.brand = brand;
this.maxSpeed = maxSpeed;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
}
}
package com.jackfrued.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.jackfrued.bean.Car;
import com.jackfrued.bean.Person;
@Configuration
public class AppConfig {
@Bean
public Car car() {
return new Car("Benz", 320);
}
@Bean
public Person person() {
return new Person("骆昊", 34);
}
}
package com.jackfrued.test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.jackfrued.bean.Person;
import com.jackfrued.config.AppConfig;
class Test {
public static void main(String[] args) {
// TWR (Java 7+)
try(ConfigurableApplicationContext factory = new AnnotationConfigApplicationContext(AppConfig.class)) {
Person person = factory.getBean(Person.class);
System.out.println(person);
}
}
}
WebApplicationContext ctx =
WebApplicationContextUtils.getWebApplicationContext(servletContext);
补充:防火墙的架设是Web安全的重要保障,ModSecurity是开源的Web防火墙中的佼佼者。企业级防火墙的架设应当有两级防火墙,Web服务器和部分应用服务器可以架设在两级防火墙之间的DMZ,而数据和资源服务器应当架设在第二级防火墙之后。
补充:敏捷软件开发的概念已经有很多年了,而且也部分的改变了软件开发这个行业,TDD也是敏捷开发所倡导的。
上一篇: 十道java易考笔试题解析
推荐阅读
-
Java基础知识面试题(2020最新版)
-
转http,https请求工具类(java)
-
java代码转PHP代码
-
Java并发编程 Synchronized及其实现原理(转)
-
面试中遇到到的简答题-1 博客分类: 面试 java面试题
-
面试中遇到选择题-1 博客分类: 面试 java面试题
-
Java面试题-基础篇一 博客分类: 面试题系列 面试javajava面试宝典Java面试题
-
2018年JAVA基础面试题和高级面试题总结 博客分类: 2018年JAVA基础面试题和高级面试题总结 java面试题
-
Java中对象拷贝方式有哪几种? 博客分类: 面试 java面试题
-
tomcat 的工作线程(转) 博客分类: Java