前几天改到一个bug:从MS SQLserver上面同步表结构并且采集数据写入其他库。然后用的核心技术是用的Hibernate。

其中bug出在SQLServer2000版本上。排查下来发现2000版本真的是一个让人头疼的数据库。

驱动jar包不兼容;hibernate5.1分页查询也不能用。系统表也与其他版本的天差地别。

一、驱动问题

一开始上网查询,发现大家都推荐用JTDS驱动。但是JTDS貌似不能与官方的Hibernate兼容,需要使用第三方Hibernate。

不然Hibernate在建立连接时会抛出驱动不能转换的异常。因为要做其他版本兼容(代码不做大改动),

所以没换成jtds的驱动(net.sourceforge.jtds.jdbc.Driver)。

然后用了ms2000的三个驱动。测试通过。但是要注意区分驱动和数据库连接信息的写法

 jdbc.drivers=com.microsoft.jdbc.sqlserver.SQLServerDriver
jdbc.url=jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=test

在查找分页问题时,偶然发现了一个更方便的方法。

资料链接:https://blog.csdn.net/hexin373/article/details/8260752

maven中央库里的sqljdbc4是不行的。这里我特地下载了 sqljdbc_3.0.1301.101_chs.tar.gz。

链接: https://pan.baidu.com/s/1CkZeHRYg5V-LxhhmVbGY8A 提取码: kwaj

用了这个jar以后,就可以把上面三个ms2000的jar包删掉了,而且驱动和数据库连接信息也可以和其他版本做统一。所以推荐这个方法

 jdbc.drivers=com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbc.url=jdbc:sqlserver://localhost:1433;DatabaseName=COREJAVA

二、系统表问题

ms2005版本以后系统表做了很多增删,比如:sys.extended_properties等。这些在ms2000都没有。

但是这个问题比较好改。

这边放出两段查询表结构的语句,不过语句还没来得及优化。

private static final String STRUCT_SQL_FORMAT = Joiner.on("\n").join(Arrays.asList(
"SELECT convert(varchar(100), h.TABLE_CATALOG) AS TABLE_CATALOG, ",
"upper(convert(varchar(100), h.TABLE_NAME)) AS TABLE_NAME, ",
"convert(varchar(100), h.TABLE_TYPE) AS TABLE_TYPE, ",
"convert(varchar(100), h.value ) AS TABLE_COMMENT, ",
"upper(convert(varchar(100), a.name)) AS COLUMN_NAME,",
"convert(varchar(100), b.name) AS COLUMN_TYPE,",
"convert(varchar(100), COLUMNPROPERTY(a.id,a.name,'PRECISION')) AS COLUMN_LENGTH,",
"convert(varchar(100), isnull(COLUMNPROPERTY(a.id,a.name,'Scale'),0)) AS NUM_SCALE,",
"convert(varchar(100), case when exists(SELECT 1 FROM sysobjects where xtype='PK' and name in (SELECT name FROM sysindexes WHERE indid in( SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid ))) then 'YES' else 'NO' end) AS IS_PRIMARYKEY, ",
"convert(varchar(100), case when a.isnullable=1 then 'YES'else 'NO' end) AS IS_NULLABLE, ",
"convert(varchar(100), isnull(g.[value],'')) AS COLUMN_COMMENT, ",
"dc.definition COLUMN_DEFAULT",
"FROM syscolumns a ",
"left join systypes b on a.xusertype = b.xusertype ",
"inner join sysobjects d on a.id=d.id and d.xtype='U' and d.name <> 'dtproperties' ",
"left join sys.extended_properties g on a.id=g.major_id and a.colid=g.minor_id ",
"left join (",
" select a.TABLE_CATALOG, a.TABLE_NAME, a.TABLE_TYPE, b.value from information_schema.tables a ",
" left join sys.extended_properties b on b.major_id = OBJECT_ID(a.TABLE_NAME) and b.minor_id = 0 ",
") h on h.TABLE_NAME = d.name ",
"LEFT JOIN sys.default_constraints dc ON d.id=dc.parent_object_id AND a.colid=dc.parent_column_id AND a.cdefault=dc.[object_id]",
"order by d.name "
)); private static final String STRUCT_SQL_FORMAT_FOR_2000 = Joiner.on("\n").join(Arrays.asList(
"select convert(varchar(100), h.TABLE_CATALOG) AS TABLE_CATALOG, ",
"upper(convert(varchar(100), h.TABLE_NAME)) AS TABLE_NAME, ",
"convert(varchar(100), h.TABLE_TYPE) AS TABLE_TYPE, ",
"convert(varchar(100), ta.TABLE_COMMENT ) AS TABLE_COMMENT, ",
"upper(convert(varchar(100), ta.NAME)) AS COLUMN_NAME, ",
"convert(varchar(100), ta.COLUMN_TYPE) AS COLUMN_TYPE, ",
"convert(varchar(100), ta.COLUMN_LENGTH) AS COLUMN_LENGTH, ",
"convert(varchar(100), isnull(ta.NUM_SCALE,0)) AS NUM_SCALE, ",
"convert(varchar(100), ta.IS_PRIMARYKEY) AS IS_PRIMARYKEY, ",
"convert(varchar(100), ta.IS_NULLABLE) AS IS_NULLABLE, ",
"convert(varchar(100), ta.COLUMN_COMMENT) AS COLUMN_COMMENT, ",
"convert(varchar(100), ta.COLUMN_DEFAULT) AS COLUMN_DEFAULT ",
" FROM ", "(SELECT ",
" TABLE_NAME = d.name, ",
" TABLE_COMMENT = isnull(f. VALUE, ''), ",
" NAME = a.name, ",
" IS_PRIMARYKEY = CASE WHEN EXISTS (SELECT 1 FROM sysobjects WHERE xtype = 'PK' AND parent_obj = a.id AND name IN ( SELECT name FROM sysindexes WHERE indid IN ( SELECT indid FROM sysindexkeys WHERE id = a.id AND colid = a.colid))) THEN 'YES' ELSE 'NO' END, ",
" COLUMN_TYPE = b.name, ",
" COLUMN_LENGTH = COLUMNPROPERTY(a.id, a.name, 'PRECISION'), ",
" NUM_SCALE = isnull(COLUMNPROPERTY(a.id, a.name, 'Scale'), 0), ",
" IS_NULLABLE = CASE WHEN a.isnullable = 1 THEN 'YES' ELSE 'NO' END, ",
" COLUMN_DEFAULT = isnull(e. TEXT, ''), ",
" COLUMN_COMMENT = isnull(g.[value], '') ",
"FROM ", "\tsyscolumns a ",
"LEFT JOIN systypes b ON a.xusertype = b.xusertype ",
"INNER JOIN sysobjects d ON a.id = d.id AND d.xtype = 'U' AND d.name <> 'dtproperties' ",
"LEFT JOIN syscomments e ON a.cdefault = e.id ",
"LEFT JOIN sysproperties g ON a.id = g.id AND a.colid = g.smallid ",
"LEFT JOIN sysproperties f ON d.id = f.id AND f.smallid = 0 ",
") ta ", "left join information_schema.tables h on h.TABLE_NAME = ta.table_name"
));

三、Hibernate5.1分页问题

最头疼的问题。我不懂是Hibernate官方根本没测试过ms2000的分页,还是我的用法有问题。

 public <T> List<T> executeQuery(final String sqlString, Integer current, Integer maxResult, Integer fetchSize, Class<T> t, Object... parameters) throws SqlExecutionException {
List<T> rowsList;
Session session = sessionLocal.get();
try {
Query query = session.createSQLQuery(sqlString);
if (current != null) {
query.setFirstResult(current);
} if (maxResult != null && maxResult > 0) {
query = query.setMaxResults(maxResult);
} if (fetchSize != null && fetchSize > 0) {
query = query.setFetchSize(fetchSize);
} if (parameters != null && parameters.length > 0) {
for (int i = 0; i < parameters.length; i++) {
query.setParameter(i, parameters[i]);
}
} rowsList = query.setResultTransformer(Transformers.aliasToBean(t)).list();
} catch (Exception e) {
throw new SqlExecutionException(sqlString, e.getCause());
} finally {
session.close();
session = null;
sessionLocal.remove();
}
return rowsList;
}

上面代码是我做的Hibernate分页封装。maxResult实际上相当于pageSize。如果maxResult为空则运行正常。但一旦指定了maxResult,就会报错。

调用上面方法:

 List<HashMap> data = session.executeQuery(
"select * from test",
0,
100,
100,
HashMap.class,
null
);

则会抛出异常:

 com.syher.hibernate.jdbc.exception.SqlExecutionException: 第 1 行: '@P0' 附近有语法错误。

什么原因导致的?明明就一条简单的查询语句啊?

跟踪Hibernate源代码到Loader的executeQueryStatement方法。

     protected SqlStatementWrapper executeQueryStatement(
String sqlStatement,
QueryParameters queryParameters,
boolean scroll,
List<AfterLoadAction> afterLoadActions,
SessionImplementor session) throws SQLException { // Processing query filters.
queryParameters.processFilters( sqlStatement, session ); // Applying LIMIT clause.
final LimitHandler limitHandler = getLimitHandler(
queryParameters.getRowSelection()
);
String sql = limitHandler.processSql( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() ); // Adding locks and comments.
sql = preprocessSQL( sql, queryParameters, getFactory().getDialect(), afterLoadActions ); final PreparedStatement st = prepareQueryStatement( sql, queryParameters, limitHandler, scroll, session );
return new SqlStatementWrapper(
st, getResultSet(
st,
queryParameters.getRowSelection(),
limitHandler,
queryParameters.hasAutoDiscoverScalarTypes(),
session
)
);
}

LimitHandler类是把我们的sql语句加工成分页语句的类。

在这里,我们的sql语句select * from test 经过limitHandler.processSql方法处理后,  会变成 select top ? * from test;

 @Override
public String processSql(String sql, RowSelection selection) {
if (LimitHelper.hasFirstRow( selection )) {
throw new UnsupportedOperationException( "query result offset is not supported" );
} final int selectIndex = sql.toLowerCase(Locale.ROOT).indexOf( "select" );
final int selectDistinctIndex = sql.toLowerCase(Locale.ROOT).indexOf( "select distinct" );
final int insertionPoint = selectIndex + (selectDistinctIndex == selectIndex ? 15 : 6); return new StringBuilder( sql.length() + 8 )
.append( sql )
.insert( insertionPoint, " TOP ? " )
.toString();
}

上网查了一下,jdbc prepareStatement预编译不支持top ?的写法。然后我特地写了个jdbc的demo验证,发现问题也确实出在jdbc。

@Test
public void run() {
try {
String sql = "SELECT TOP ?* FROM test";
Connection conn = getJDBCConnection();
PreparedStatement pst = conn.prepareStatement(sql);
pst.setInt(1, 10);
ResultSet rs = pst.executeQuery();
} catch (Exception e) {
e.printStackTrace();
}
} public static Connection getJDBCConnection() throws IOException {
Connection conn = null;
String drivers = "com.microsoft.sqlserver.jdbc.SQLServerDriver";
//String drivers = "com.microsoft.jdbc.sqlserver.SQLServerDriver";
String url = "jdbc:sqlserver://192.168.2.173:1433;DatabaseName=dbtest";
//String url = "jdbc:microsoft:sqlserver://192.168.2.173:1433;DatabaseName=dbtest";
//String url = "jdbc:sqlserver://192.168.3.104:1433;DatabaseName=testdb";
String userName = "sa";
String password = "sa@173";
//String password = "sa@104";
if (drivers != null) {
try {
Class.forName(drivers).newInstance();
conn = DriverManager.getConnection(url, userName, password); } catch (Exception e) {
e.printStackTrace();
System.out.println("数据库连接失败");
}
} else {
System.out.println("数据库驱动不存在");
}
return conn;
}

上面demo里,ms2008、ms2016都不会有问题。而只有ms2000抛出了“@P0' 附近有语法错误。”的异常。

抓耳挠腮了两天之后,终于在一篇博客里面找到了灵感。

参考资料:https://gcy6164.iteye.com/blog/1160119

一开始看到这篇资料时,我是确实按着博客步骤改,结果没用。遂放弃。

然后某天在TopLimitHandler类中看到了博客中的supportsLimit方法,才恍然大悟。原来是自己改错了方向。

我没看过Hibernate3.2的源码,但是大致猜测Hibernate3.2中应该是没有LimitHandler类的。所以Hibernate3.2中判断

数据库是否支持分页是在SQLServerDialect中。而Hibernate5.1时为了更好的扩展,增加了LimitHandler专门处理分页语句的接口。

而判断数据库是否支持分页的方法也转移到了这个类中。

因此Hibernate3.2的修改教程不适合Hibernate5.1。但其实是同一个解决思路。

于是我自定义了一个Hibernate方言继承了SQLServerDialect,并重写了getLimitHandler方法。

 public class SQLServer2000Dialect extends SQLServerDialect {

     public SQLServer2000Dialect() {
super();
} @Override
public LimitHandler getLimitHandler() {
return new SQLServer2000LimitHandler(false, false);
}
}

自定义了SQLServer2000LimitHandler类,并修改了supportsLimit方法。

 public class SQLServer2000LimitHandler  extends TopLimitHandler {
public SQLServer2000LimitHandler(boolean supportsVariableLimit, boolean bindLimitParametersFirst) {
super(supportsVariableLimit, bindLimitParametersFirst);
} @Override
public boolean supportsLimit() {
return false;
}
}

打包,调试。果然没问题了。

我的hibernate测试代码:

https://github.com/rxiu/study-on-road/tree/master/trickle-hibernate

Hibernate5.1+Sqlserver2000分页查询的更多相关文章

  1. MySQL、SQLServer2000(及SQLServer2005)和ORCALE三种数据库实现分页查询的方法

    在这里主要讲解一下MySQL.SQLServer2000(及SQLServer2005)和ORCALE三种数据库实现分页查询的方法. 可能会有人说这些网上都有,但我的主要目的是把这些知识通过我实际的应 ...

  2. Hibernate5.2之QBC查询

                                                         Hibernate5.2值QBC查询 一.简介  Hibenate的QBC查询个人认为是Hib ...

  3. Hibernate5.2之HQL查询

    Hibernate5.2之HQL查询                                                                  一. 介绍 Hibernate的 ...

  4. MySQL、SqlServer、Oracle三大主流数据库分页查询

    在这里主要讲解一下MySQL.SQLServer2000(及SQLServer2005)和ORCALE三种数据库实现分页查询的方法.可能会有人说这些网上都有,但我的主要目的是把这些知识通过我实际的应用 ...

  5. MySQL、SqlServer、Oracle三大主流数据库分页查询 (MySQL分页不能用top,因为不支持)

    一. MySQL 数据库 分页查询MySQL数据库实现分页比较简单,提供了 LIMIT函数.一般只需要直接写到sql语句后面就行了.LIMIT子 句可以用来限制由SELECT语句返回过来的数据数量,它 ...

  6. JdbcTemplate+PageImpl实现多表分页查询

    一.基础实体 @MappedSuperclass public abstract class AbsIdEntity implements Serializable { private static ...

  7. 用Hibernate和Struts2+jsp实现分页查询、修改删除

    1.首先用get的方法传递一个页数过去 2.通过Struts2跳转到Action 3.通过request接受主页面index传过的页数,此时页数是1, 然后调用service层的方法获取DAO层分页查 ...

  8. MySQL、Oracle和SQL Server的分页查询语句

    假设当前是第PageNo页,每页有PageSize条记录,现在分别用Mysql.Oracle和SQL Server分页查询student表. 1.Mysql的分页查询: SELECT * FROM s ...

  9. 分页查询和分页缓存查询,List<Map<String, Object>>遍历和Map遍历

    分页查询 String sql = "返回所有符合条件记录的待分页SQL语句"; int start = (page - 1) * limit + 1; int end = pag ...

随机推荐

  1. Android-隐式意图激活操作系统通话界面

    阅读Android操作系统的 packages/apps/phone/AndroidManifest.xml,是如何暴露的   AndroidManifest.xml Android操作系统在这里明确 ...

  2. 微服务中的健康监测以及其在ASP.NET Core服务中实现运行状况检查

    1 .什么是健康检查? 健康检查几乎就是名称暗示的.它是一种检查您的应用程序是否健康的方法.随着越来越多的应用程序转向微服务式架构,健康检查变得尤其重要(Health Check).虽然微服务架构有很 ...

  3. ArcGIS(批量)删除属性字段

    ArcGIS下删除属性字段有两种方式:① 单个删除:② 批量删除. 单个删除 批量删除 尽管如此,ArcGIS桌面软件在属性字段的编辑上并不太方便,所以我们自己做了一些工具辅助平时的内业处理工作.(* ...

  4. CORS 跨域请求

    一.简介 CORS需要浏览器和服务器同时支持.目前,所有浏览器都支持该功能,IE浏览器不能低于IE10. 整个CORS通信过程,都是浏览器自动完成,不需要用户参与.对于开发者来说,CORS通信与同源的 ...

  5. R语言中Fisher判别的使用方法

    最近编写了Fisher判别的相关代码时,需要与已有软件比照结果以确定自己代码的正确性,于是找到了安装方便且免费的R.这里把R中进行Fisher判别的方法记录下来. 1. 判别分析与Fisher判别 不 ...

  6. leetcode 移除元素

    给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成 ...

  7. html中事件调用JavaScript函数时有return与没有return的区别

    JAVASCRIPT在事件中调用函数时用return返回值实际上是对window.event.returnvalue进行设置. 而该值决定了当前操作是否继续.当返回的是true时,将继续操作.当返回是 ...

  8. 深入了解java虚拟机(JVM) 第九章 class文件结构及意义

    Class文件是访问jvm的重要指令,学习了class文件能够更加深入的了解jvm的工作过程.本文只进行概况总结,要进行更加详细的学习class文件,可以参考:https://blog.csdn.ne ...

  9. 【Oracle 12c】CUUG OCP认证071考试原题解析(35)

    35.choose the best answer View the Exhibit and examine the description of the EMPLOYEES table. Evalu ...

  10. 微信小程序—相对路径和绝对路径

    一.绝对路径: 以“/”开头代表根目录, /ada/like/index 二.相对路径 ../../ada/like/index 或者: ada/like/index