前几天改到一个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. Spring 之 AOP

    面向方面的编程,即 AOP,是一种编程技术,它允许程序员对横切关注点或横切典型的职责分界线的行为(例如日志和事务管理)进行模块化.AOP 的核心构造是方面, 它将那些影响多个类的行为封装到可重用的模块 ...

  2. ZooKeeper 一致性协议 ZAB 原理

    一致性协议有很多种,比如 Paxos,Raft,2PC,3PC等等,今天我们讲一种协议,ZAB 协议,该协议应该是所有一致性协议中生产环境中应用最多的了.为什么呢?因为他是为 Zookeeper 设计 ...

  3. 简单配置vps,防ddos攻击

    防人之心不可无. 网上总有些无聊或者有意的人.不多说了.上干货,配置vps apf防小流量ddos攻击. 对于大流量的ddos攻击, 需要机房的硬件防火墙,vps内部可能也扛不住. 1. 安装 DDo ...

  4. jenkins启动失败,提示Starting Jenkins Jenkins requires Java8 or later, but you are running 1.7.0

    # 背景 centos安装jenkins后,先启动jenkins服务,结果报错如下: 但自己明明已经安装了java8的 # 解决方法 既然安装了java8的话,那么证明是jenkins启动的是还是用的 ...

  5. (zxing.net)一维码UPC E的简介、实现与解码

    UPC(Universal Product Code)码是最早大规模应用的条码,其特性是一种长度固定.连续性的条  码,目前主要在美国和加拿大使用,由于其应用范围广泛,故又被称万用条码. UPC码仅可 ...

  6. (转载)Oracle的悲观锁和乐观锁

    为了得到最大的性能,一般数据库都有并发机制,不过带来的问题就是数据访问的冲突.为了解决这个问题,大多数数据库用的方法就是数据的锁定. 数据的锁定分为两种方法,第一种叫做悲观锁,第二种叫做乐观锁.什么叫 ...

  7. 七、linux目录结构知识---实战

    1.企业面试题:一个100M的磁盘分区,分别写入1k文件,及写入1M的文件,分别可以写多少个? 一块磁盘被分区格式化成系统文件后,有Inode和Block:一个文件一般占用一个Inode和一个Bloc ...

  8. 1415: 小ho的01串 [字符串]

    点击打开链接 1415: 小ho的01串 [字符串] 题目描述 有一个由0和1组成的字符串,它好长呀--------一望无际 恩,说正题,小ho的数学不太好,虽然是学计算机的但是看见0和1也是很头疼的 ...

  9. iphone 自学常用网址

    https://www.gitbook.com/book/numbbbbb/-the-swift-programming-language-/details https://github.com/ip ...

  10. 为服务器设置固定IP地址

    为服务器设置固定IP地址 1.获取超级管理员权限 命令:$ su - 输入root密码 2.判断哪个网卡有流量,或者确定需要设置哪个网卡的固定ip 命令:# ifconfig PS:可以查询哪些网卡有 ...