分类目录归档:互联网后端技术

apache-derby-数据库使用注意事项


Apache Derby数据库是一个小巧的、支持内嵌模式的数据库,他实现了SQL-99 和 SQL-2003 标准。最近由于项目的原因,开始使用Derby,以下是一些使用心得:

  1. SQL语句不支持rownum关键字

    虽然不直接支持rownum关键字,但是可以通过ROW_NUMBER函数实现。用法如下:

    SELECT * FROM (
      SELECT
        ROW_NUMBER() OVER () AS rownum,
        columns
      FROM tablename
    ) AS foo
    WHERE rownum

    参考:OLAPRowNumber

  2. 修改derby.log文件存储目录

    derby.log记录了数据库启动、运行、结束的日志信息,这个日志文件默认并不会跟随着数据库存储,而是存放在了工程根目录下。通过设置derby.system.home属性,我们可以解决derby.log日志文件存储路径的问题。在Java应用启动的时候增加一下参数:

    -Dderby.system.home=/home/leo/derby

    系统启动之后,derby.log文件就会存放在/home/leo/derby目录下

  3. 关闭Derby数据库

    Apache Derby在作为内嵌数据库运行的时候, 我们需要在系统退出之前停止Derby数据库。方法如下:

    DriverManager.getConnection("jdbc:derby:;shutdown=true");

    可以直接用以上语句停止数据库。执行停止命令时,数据库名称不是必须的。Apache Derby数据库在停止成功后,会抛出java.sql.SQLException,这是正常的。 更详细的描述请参考:Shutting down the system

  4. 同一个JVM实例中如何重启Derby

    首先按照第三步关闭数据库,然后执行下面的代码,需要特别注意的是newInstance()方法。重新注册驱动,必须增加newInstance()方法。

    Class.forName(org.apache.derby.jdbc.EmbeddedDriver).newInstance();

    或者增加 deregister=false属性,然后关闭数据库。通过设置deregister=false属性后,可以避免重新注册驱动。

    DriverManager.getConnection("jdbc:derby:;shutdown=true;deregister=false");

FWK005 parse may not be called while parsing.


最近在使用javax.xml.parsers.DocumentBuilder解析xml文件的时候偶尔会出错:

org.xml.sax.SAXException: FWK005 parse may not be called while parsing.
        at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:263)
        at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:284)
        at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:208)
...

跟踪了一下代码,发现这个异常是在com.sun.org.apache.xerces.internal.parsers.DTDConfiguration.parse(DTDConfiguration.java:546)抛出来的。该段代码结构如下:

if(fParseInProgress) {
    throw new XNIException("FWK005 parse may not be called while parsing.");
}

fParseInProgress = true;

// 解析xml文件

finally {
    fParseInProgress = false;
}

从程序逻辑来看,如果当前DocumentBuilder对象正在转换文档,此时再次请求转换文档,那么直接抛出XNIException(“FWK005 parse may not be called while parsing.”);异常。

这个问题也比较好解决,一种是对转换xml文档的方法,增加synchronized关键字,这样子不会有两个线程同时访问方法。

还有一种方法是创建一个DocumentBuilder类型的ThreadLocal变量,这样子每个线程都拥有自己的DocumentBuilder对象,能够同时转换多个xml文件。代码如下:

private static ThreadLocal docBuildeIns = new ThreadLocal() {
    protected DocumentBuilder initialValue() {
        try {
            return DocumentBuilderFactory.newInstance().newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            String msg = "DocumentBuilder 对象初始化失败!";
            log.error(msg, e);
            throw new IllegalStateException(msg, e);
        }
    }
};

解析xml文件时的调用方法:

docBuildIns.get().parse(File);

get()方法返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值。

Spring 3.0配置多个DispatcherServlet的方法及注意事项


项目中需要同时用到两个视图解析器,一个报表的,一个jsp的。这就产生了标题所述的需求。通过阅读Spring Framework参考手册以及示例,解决了这个问题,中间也走了不少弯路,碰到了不少问题,特记录下来。

配置多个DispatcherServlet有多种方法,一种是在DispatcherServlet中直接指定此DispatcherServlet对应的配置文件。例如:

<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/app-config.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>

但是这种配置方法存在一个问题:由于DispatcherServlet对应的Controller需要访问Service并且使用dataSource,而DispatcherServlet之间的上下文是分离的,势必会出现多个dataSource的情况,降低了数据库连接使用的效率。

后来找到了第二种方法,目前正在使用的:DispatcherServlet不指定具体的配置文件,并且DispatcherServlet只初始化自己需要使用到的Controller类,然后由org.springframework.web.context.ContextLoaderListener初始化除Controller以外的全部对象。这样子多个dataSource的问题解决了。配置如下: 继续阅读

Spring MVC 如何防止XSS、SQL注入攻击


在Web项目中,通常需要处理XSS,SQL注入攻击,解决这个问题有两个思路:

  • 在数据进入数据库之前对非法字符进行转义,在更新和显示的时候将非法字符还原
  • 在显示的时候对非法字符进行转义

如果项目还处在起步阶段,建议使用第二种,直接使用jstl的<c:out>标签即可解决非法字符的问题。当然,对于Javascript还需要自己处理一下,写一个方法,在解析从服务器端获取的数据时执行以下escapeHTML()即可。

附:Javascript方法:

String.prototype.escapeHTML = function () {
return this.replace(/&/g, ‘&amp;’).replace(/>/g, ‘&gt;’).replace(/</g, ‘&lt;’).replace(/”/g, ‘&quot;’);
}

如果项目已经开发完成了,又不想大批量改动页面的话,可以采用第一种方法,此时需要借助Spring MVC的@InitBinder以及org.apache.commons.lang.PropertyEditorSupport、org.apache.commons.lang.StringEscapeUtils

public class StringEscapeEditor extends PropertyEditorSupport {
private boolean escapeHTML;
private boolean escapeJavaScript;
private boolean escapeSQL;

public StringEscapeEditor() { super(); }
public StringEscapeEditor(boolean escapeHTML, boolean escapeJavaScript, boolean escapeSQL) {
super();
this.escapeHTML = escapeHTML;
this.escapeJavaScript = escapeJavaScript;
this.escapeSQL = escapeSQL;
}

@Override
public void setAsText(String text) {
if (text == null) {
setValue(null);
} else {
String value = text;
if (escapeHTML) {   value = StringEscapeUtils.escapeHtml(value);     }
if (escapeJavaScript) {     value = StringEscapeUtils.escapeJavaScript(value);     }
if (escapeSQL) {     value = StringEscapeUtils.escapeSql(value);     }     setValue(value);     }
}

@Override
public String getAsText() {     Object value = getValue();     return value != null ? value.toString() : “”;    }
}

在使用StringEscapeUtils时需要注意escapeHtml和escapeJavascript方法会把中文字符转换成Unicode编码,如果通过<c:out>标签或者EL表达式展示时,能够正确还原,但是如果使用类似于Ext这样的前端组件来展示这部分内容时,不能正常还原,这也是我为什么放弃了第一种方法,直接使用第二种方法的原因。

在上面我们做了一个EscapeEditor,下面还要将这个Editor和Spring的Controller绑定,使服务器端接收到数据之后能够自动转移特殊字符。
下面我们在@Controller中注册@InitBinder

@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringEscapeEditor(false, false, false));
}

这个方法可以直接放到abstract Controller类中,这样子每个Controller实例都能够拥有该方法。至此第二种方法完成,但是在还原的方法暂时还没有。O(∩_∩)O…

Spring MVC JasperReport 导出Html格式报表图片不显示


最近这个项目使用了JasperReport组件,最开始使用的时候,是配置成了由org.springframework.web.servlet.view.jasperreports.JasperReportsMultiFormatView对报表模板进行解析,这样的配置可以带来一些好处,比如可以根据format关键字不同,动态生成html,pdf,xls,csv四种类型的报表,但是最近在使用的时候碰到问题了。

设计了一种图表结合的报表,使用html,pdf导出的时候图片能够正常显示 ,但是导出html格式后,报表中的图不见了。

在网上查找后发现需要设置下面四个参数:

  • JRHtmlExporterParameter.IMAGES_DIR_NAME
  • JRHtmlExporterParameter.IMAGES_URI
  • JRHtmlExporterParameter.IS_USING_IMAGES_TO_ALIGN
  • JRHtmlExporterParameter.IS_OUTPUT_IMAGES_TO_DIR

一开始是想结合Spring MVC的配置设置的,但是使用exporterParameters无法将上述参数设置到JasperReportHtmlView中,只好作罢。动手自己写了一个。步骤如下:

  1. 获得报表模板存放路径。
    String jasperFilePath = new StringBuilder(“WEB-INF/reports/”).append(jasperFileName).append(“.jasper”).toString();
  2. 获得报表模板文件
    File reportFile = getWebApplicationContext(request).getResource(jasperFilePath).getFile();
  3. 根据报表模板文件生成报表打印对象
    JasperPrint print = JasperFillManager.fillReport(reportFile.getAbsolutePath(), model, dataSource.getConnection());
  4. 创建Html导出对象
    JRHtmlExporter exporter = new JRHtmlExporter();
  5. 设置图片文件存放路径,此路径为服务器上的绝对路径
    String imageDIR = webContext.getResource(“reportFiles”).getFile().getAbsolutePath();
    exporter.setParameter(JRHtmlExporterParameter.IMAGES_DIR_NAME, imageDIR);
  6. 设置图片请求URI
    String imageURI = request.getContextPath() + “/reportFiles/”;
    exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI, imageURI);
  7. 设置导出图片到图片存放路径
    exporter.setParameter(JRHtmlExporterParameter.IS_USING_IMAGES_TO_ALIGN, Boolean.TRUE);
    exporter.setParameter(JRHtmlExporterParameter.IS_OUTPUT_IMAGES_TO_DIR, Boolean.TRUE);
  8. 设置导出对象
    exporter.setParameter(JRExporterParameter.JASPER_PRINT, print);
  9. 设置导出方法
    exporter.setParameter(JRExporterParameter.OUTPUT_WRITER, response.getWriter());
  10. 防止图片第一次不显示
    request.getSession().setAttribute(ImageServlet.DEFAULT_JASPER_PRINT_SESSION_ATTRIBUTE, print);
  11. 设置HTTP Head
    response.setContentType(“text/html”);
  12. 执行导出
    exporter.exportReport();

至此,自定义的导出方法完成了。 需要特别注意的是JRHtmlExporterParameter.IMAGES_DIR_NAME和JRHtmlExporterParameter.IMAGES_URI虽然执行的是相同的目录,但是表达方式不一样,IMAGES_DIR_NAME是文件系统的绝对路径,而IMAGES_URI是http请求时的uri。

解决Hibernate One-to-one 关系 “No row with the given identifier exists:” 错误


在项目中使用Hibernate One-to-one配置了一些表关系。主表为A表,附带了两个One-to-one关系的字表:B表、C表。

在业务上,A表插入和更新的时候会同时更新B表,但是C表使用另外的业务维护。最近在进行级联删除的时候,提示:

No row with the given identifier exists:

出现这个错误的原因是,删除的时候查找C表数据时,没有找到数据。要解决这个问题的关键点是,在级联删除时,如果one-to-one子表不存在数据,则不进行删除。关于这块的信息在Hibernate手册中并未提及,只是有一个constrained字段设置为true|flase的时候会影响删除的顺序。在A表中尝试把C表的one-to-one关系constrained字段设置为false,再次对A表进行级联删除时,C表有对应的数据才会执行删除操作,无数据时不会执行删除操作,问题解决。

有一点疑问是,在Hibernate中many-to-one这样的关系中可以设置not-null属性,为什么one-to-one属性不能也使用这样的属性呢?使用一个constrained这样的字段,怪别扭的。

——看来只能通过阅读源代码才能知道one-to-one的constrained属性究竟做了什么了。

Spring+BlazeDS+Flex+SpringSecurity配置


最近一个项目用到了Spring +BlazeDS+Flex+SpringSecurity的配置,在这里记录一下此种框架的配置方法,仅作备忘。

web.xml

<!– 初始化spring关联配置,比如SpringSecurity, Hibernate等等 –>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/*.xml
</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!– 日志服务监听器 –>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/log4j.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<!– flexhttp请求监听器 –>
<listener>
<listener-class>flex.messaging.HttpFlexSession</listener-class>
</listener>
<!– SpringSecurity HTTP访问过滤器,实现HTTP请求拦截 –>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!– 编码过滤器,经过测试,flex在进行数据传输的时候,不需要此过滤器也能正确传输中文,此处主要是为了非flex类型的请求配置 –>
<filter>
<filter-name>encoding-filter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!– SpringMVC REST服务需要,因为浏览器的HTTP不支持PUT,DELETE METHOD,通过这个过滤器可以使用POST伪装PUT,DELETE METHOD实现 –>
<filter>
<filter-name>httpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>httpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!– Hibenate 过滤器,保证一个HTTP请求使用同一个Session,并且提供了flushMode,使程序员在开发的时候不必关心缓存刷新的问题 –>
<filter>
<filter-name>hibernateFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
<init-param>
<param-name>singleSession</param-name>
<param-value>true</param-value>
</init-param> 继续阅读