JAVA 相关

达梦数据库通过 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>
//User.java
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);// 创建表结构,第一个为True就是把DDL语句输出到控制台,第二个为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();
}
}
}
}

//根据主键id删除数据
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);//根据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);//例如:sql="from User",User为类名
return query.list();
}
}

达梦数据库 JDBC 如何开启 log 日志

在分析一些疑难的 jdbc 程序问题的时候,达梦数据库可以开启 JDBC 日志,从驱动层面提供更详细的信息

开启达梦 JDBC 日志,只需要在 URL 串中加入两个参数即可,例如:

jdbc:dm://127.0.0.1:5236?logLevel=all&logDir=d:\jdbclog

logLevel 表示日志级别:

日志按从低到高依次如下(off:不记录;error:只记录错误日志;warn:记录警告信息;sql:记录 sql执行信息;info:记录全部执行信息;all:记录全部),高级别同时记录低级别的信息。

logDir 表示存放 JDBC 日志的路径,可以是相对路径也可以是绝对路径

如果 URL 是在 XML 文件中存放的,那就需要对 & 进行转义,例如

jdbc:dm://127.0.0.1:5236?logLevel=all&amp;logDir=d:\jdbclog

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 中指定。

  • mybatis 设置 timeout

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" ...>
  • JPA 中设置

1、为当前查询设定 timeout:

String sql = "SELECT ...";  
TypedQuery<User> query = entityManager.createQuery(sql, User.class);
query.setParameter("id", id);
//此处设置timeout,单位:毫秒
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>
  • JNDI 方式配置

通常情况下,我们线上的数据库 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 默认使用的是自增列的方式,需要修改对应的表结构,将相应的列改为自增列,即可。

微信扫码
分享文档
扫一扫
联系客服