达梦数据库通过 hibernate 连接的基本程序配置 //hibernate.cfg.xml <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd" > <hibernate-configuration > <session-factory > <property name ="hibernate.connection.driver_class" > dm.jdbc.driver.DmDriver</property > <property name ="hibernate.connection.url" > jdbc:dm://localhost:5236</property > <property name ="hibernate.connection.username" > SYSDBA</property > <property name ="hibernate.connection.password" > SYSDBA</property > <property name ="hibernate.dialect" > org.hibernate.dialect.DmDialect</property > <mapping resource ="dmhibernate/User.hbm.xml" /> </session-factory > </hibernate-configuration >
//User.hbm.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" > <hibernate-mapping package ="dmhibernate" > <class name ="User" table ="dmuser" > <id name ="id" column ="id" > </id > <property name ="name" /> <property name ="password" /> </class > </hibernate-mapping >
ackage dmhibernate; public class User { private int id; private String name; private String password; public int getId () { return id; } public void setId (int id) {this .id = id;} public String getName () {return name;} public void setName (String name) {this .name = name;} public String getPassword () {return password;}
import java.util.List; import java.io.Serializable;import org.hibernate.Query;import org.hibernate.Session;import org.hibernate.SessionFactory;import org.hibernate.cfg.Configuration;import org.hibernate.tool.hbm2ddl.SchemaExport;public class HibernateTest {Configuration cfg = new Configuration().configure(); SessionFactory factory = cfg.buildSessionFactory(); Session session = null ; public void createTable () {Configuration cfr = new Configuration().configure(); SchemaExport export = new SchemaExport(cfr); export.create(true , true ); } public void insertTable (User users) {try {session = factory.openSession(); session.beginTransaction(); session.save(users); session.beginTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { if (session != null ) {if (session.isOpen()) {session.close(); } } } } public void deleteTable (Serializable id) {try {session = factory.openSession(); session.beginTransaction(); User news = (User) session.get(User.class, id); session.delete(news); session.beginTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { if (session != null ) {if (session.isOpen()) {session.close(); } } } } public void updateTable (User users) {try {session = factory.openSession(); session.beginTransaction(); User news = (User) session.get(User.class, id); session.update(news); session.beginTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { if (session != null ) {if (session.isOpen()) {session.close(); } } } } public void updateTableByColumn (int id, String name) {try {session = factory.openSession(); session.beginTransaction(); User users = (User) session.get(User.class, id); users.setName(name); session.save(users); session.beginTransaction().commit(); } catch (Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { if (session != null ) {if (session.isOpen()) {session.close(); } } } } public List<User> queryTable (String sql) {session = factory.openSession(); session.beginTransaction(); Query query = session.createQuery(sql); return query.list(); } }
达梦数据库 JDBC 如何开启 log 日志 在分析一些疑难的 jdbc 程序问题的时候,达梦数据库可以开启 JDBC 日志,从驱动层面提供更详细的信息
开启达梦 JDBC 日志,只需要在 URL 串中加入两个参数即可,例如:
logLevel 表示日志级别:
日志按从低到高依次如下(off:不记录;error:只记录错误日志;warn:记录警告信息;sql:记录 sql执行信息;info:记录全部执行信息;all:记录全部),高级别同时记录低级别的信息。
logDir 表示存放 JDBC 日志的路径,可以是相对路径也可以是绝对路径
如果 URL 是在 XML 文件中存放的,那就需要对 & 进行转义,例如
Communication 通信错误问题分析 页面上有个功能不定期的报错 Communication error:
碰到这种问题我们第一时间怀疑是网络问题,但是客户端工具可以正常连数据库,页面上其他功能也没报错。
所以可以排除网络问题,为方便跟踪问题,我们可以开启 DM JDBC 的日志,获取更详细的信息
[ERROR - 2020 -04 -26 11 :36 :58 ] tid:262 { conn-33 , pstmt-3706 } execute(); dm.jdbc.driver.DMException: Communication error at dm.jdbc.driver.DBError.throwException(DBError.java:683 ) at dm.jdbc.c.a.a(DBAccess.java:897 ) at dm.jdbc.c.a.a(DBAccess.java:365 ) at dm.jdbc.driver.DmdbPreparedStatement.executeInner(DmdbPreparedStatement.java:212 ) at dm.jdbc.driver.DmdbPreparedStatement.do_execute(DmdbPreparedStatement.java:301 ) at dm.jdbc.filter.FilterChain.PreparedStatement_execute(FilterChain.java:5498 ) at dm.jdbc.filter.log.LogFilter.PreparedStatement_execute(LogFilter.java:8061 ) at dm.jdbc.filter.FilterChain.PreparedStatement_execute(FilterChain.java:5494 ) at dm.jdbc.driver.DmdbPreparedStatement.execute(DmdbPreparedStatement.java:1625 )
通过语句句柄:pstmt-3706 可以找到报错的 SQL 语句和它的参数:
然后可以根据语句和参数自己写一个小程序进行验证,发现并不会报错,SQL 单独在客户端中执行也不会报错,这就有点费解了。
接下来我们查一查数据库的 SQL 日志,看看是什么情况:
从 SQL 日志可以看出报错是 -6007:连接异常,并且是在执行几秒钟之后,这个很大的可能性是 SQL 超时然后断开了连接,就有两个可能性:
数据库端对用户设置了资源限制
应用对 SQL 设置了超时
经过排查,我们发现数据库并未对用户设置资源限制,仔细分析 JDBC 日志发现应用调用了 JDBC 的接口设置了连接限制:
--JDBC 日志中有很多类似的信息,说明应用针对不同 SQL 设置了不同的超时时间 setNetworkTimeout(ThreadPoolExecutor, Integer); [PARAMS]: java.util.concurrent.ThreadPoolExecutor@49e62b17[Running, pool size = 1 , active threads = 0 , queued tasks = 0 , completed tasks = 111 ], 5000 ; setNetworkTimeout(ThreadPoolExecutor, Integer); [PARAMS]: java.util.concurrent.ThreadPoolExecutor@49e62b17[Running, pool size = 1 , active threads = 1 , queued tasks = 0 , completed tasks = 110 ], 15000 ;
可是询问客户的开发人员,他们说没有对 SQL 设置超时时间,只在连接池中做了配置,如下:
显然这个设置和我们分析到的现象不太一致,JDBC 日志里面可以看了到应用多次调用接口设置了超时时间,并且每次时间不太一致,应该另有原因,JDBC 中还可以用什么方式设置超时,可以参考:查询超时时间设置
很快客户开发人员就从代码里面找到了超时的设置。
治标的方法:修改这个超时的配置,将时间改大一点。
治本就要分析一下这个 SQL,把它进一步优化一下。
查询超时时间设置 我们有时候需要控制 SQL 查询的最大耗时,比如一个“执行时长”的 SQL 在指定时间内如果没有执行完毕,我们需要“取消”此 SQL,我们知道在 JDBC 中 Statement 类可以通过 setQueryTimeout() 来实现此特性。
当设置 query timeout 之后,JDBC 客户端发送请求,并等待直到执行完成或者超时,当超时后,客户端尝试 cancel 当前 SQL,要求数据库中断执行,因为网络通讯需要时间,可能在客户端尝试 cancel 时,数据库已经执行成功,此时请求将会返回(而不是取消);超时后取消成功,那么当前客户端调用将会抛出 SQLTimeoutException。
queryTimeout 选项,目前不能在 JDBC Url 中作为 properties 传入,所以不能像 socketTimeout、connectionTimeout 参数那样可以在 URL 中指定。
1、在 mybatis-config.xml 中设置:此处设置对全局的所有 sql 都生效,包括 insert、select、update 等。
<settings > <setting name ="defaultStatementTimeout" value ="25" /> </settings >
2、在 statement 语句中设置:只对当前 statement 有效,select、insert、update 等语句中都有 timeout 属性
<select id ="selectPerson" timeout ="10000" ... >
1、为当前查询设定 timeout:
String sql = "SELECT ..." ; TypedQuery<User> query = entityManager.createQuery(sql, User.class); query.setParameter("id" , id); query.setHint("javax.persistence.query.timeout" , 20000 );
2、JPA 全局配置
<bean id ="entityManagerFactory" class ="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" > <property name ="jpaProperties" > <props > <prop key ="eclipselink.cache.shared.default" > false</prop > <prop key ="eclipselink.weaving" > false</prop > <prop key ="javax.persistence.query.timeout”>20000<prop/> </props> </property> </bean>
通常情况下,我们线上的数据库 datasource 管理是基于 JNDI 的方式,当然我们可以在 JNDI 中管理此选项,本文基于 tomcat-pool 的方式:
<Resource name="jdbc/masterDB" jdbcInterceptors="QueryTimeoutInterceptor(queryTimeout=20000)" /> ##单位:秒,默认不开启 timeout 参数
我们只需要在 Resource 配置中增加一个 jdbcInterceptors 即可。
hibernate 使用序列作为主键在国产达梦数据库中的配置方法 hibernate 有多种生成主键策略,例如 assigned、increment、hilo、seqhilo、sequence、identity、native、uuid、guid 等方法,其中 native 由 hibernate 根据使用的数据库自行判断采用 identity、hilo、sequence 其中一种作为主键生成方式,灵活性很强。如果能支持 identity 则使用 identity,如果支持 sequence 则使用 sequence。MySQL 使用 identity,Oracle 使用 sequence。对应 hbm.xml 中的配置也相对简单,如下。
<id name ="id" column ="id" > <generator class ="native" /> </id >
但是这个配置是不能在 DM 数据库中识别的,会直接报错,违反非空约束。所以需要单独指定成使用序列作为主键的方式,同时要在数据库中先手动创建对应的序列,再进行序列名配置后引用。
CREATE SEQUENCE HIBERNATE_SEQUENCE INCREMENT BY 1 START WITH 1 CACHE 20 ;
<id name ="ID_" type ="long" > <column name ="ID_" /> <generator class ="native" > <param name ="sequence" > HIBERNATE_SEQUENCE</param > </generator > </id >
在达梦数据库中,native 默认使用的是自增列的方式,需要修改对应的表结构,将相应的列改为自增列,即可。