前几天改到一个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. [c# 20问] 2.如何转换XML文件

    添加System.Xml引用 使用XmlReader转换字符串 DEMO #region Parse Xml private static void ParseXml(string xmlString ...

  2. Hadoop各个组件与端口

    组件 Daemon 端口 配置 说明 HDFS DataNode 50010 dfs.datanode.address datanode服务端口,用于数据传输 HDFS DataNode 50075 ...

  3. sqlhelper写调用存储过程方法

    public static object Proc(string ProcName, SqlParameter[] parm) { conn.Open(); //最后一个参数为输出参数 parm[pa ...

  4. eFrameWork学习笔记-eList

    HTML: <div style="margin:8px;"> <h1>.不分页</h1> <asp:Repeater id=" ...

  5. Java并发之ReentrantLock详解

    一.入题 ReentrantLock是Java并发包中互斥锁,它有公平锁和非公平锁两种实现方式,以lock()为例,其使用方式为: ReentrantLock takeLock = new Reent ...

  6. dockerfile 构建tomcat

    事先下载好tomcat和jdk的二进制包. 下载地址https://pan.baidu.com/s/1kWWHGEV 值得一说的是 tomcat的官方镜像 剪切了很多jdk和系统命令,所以生产环境建议 ...

  7. Delphi XE7编译安卓程序出错了

    昨天编译一个先前可以正常运行的程序,忽然就不能编译了,总是提示这个错误,经过一番排查,终于搞定了,原因:删除了安卓lib引用的JAR和单元文件.如果你也出现这个问题,打开工程全部目录,看一下是否有打小 ...

  8. 有效单词词广场——算法面试刷题5(for google),考察数学

    给定一个单词序列,检查它是否构成一个有效单词广场.一个有效的单词广场应满足以下条件:对于满足0≤k<max(numRows numColumns)的k,第k行和第k列对应的字符串应该相同,. 给 ...

  9. 查看npm全局安装位置

    查看npm全局安装位置:npm config get prefix 设置位置:npm config set prefix 填写位置

  10. Python之机器学习K-means算法实现

    一.前言: 今天在宿舍弄了一个下午的代码,总算还好,把这个东西算是熟悉了,还不算是力竭,只算是知道了怎么回事.今天就给大家分享一下我的代码.代码可以运行,运行的Python环境是Python3.6以上 ...