赞
踩
本来项目组使用的数据库是Oracle,如今要切换到达梦。。。早期的时候就讨论过数据库日志迁移的问题,本来以为是一个小问题可以随便搞定,没想到是踩坑了,记录下:
简单来说,如果你现在已经实现了Oracle的自定义日志,数据库迁移到达梦后用同一套代码报错,那么就是达梦没有创建主键,只需要创建一个主键。并在自己实现的 DBAppender 里面重写一下这个方法即可:
- @Override
- protected Method getGeneratedKeysMethod() {
- try {
- return PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
- } catch (Exception ex) {
- return null;
- }
- }
但如果啥都没实现,那么就继续看下去吧。
lomback.xml主要配置信息(只展示数据库连接部分配置)
- <!-- 输出日志到数据库-->
- <appender name="DB" class="org.cloud.common.config.MyDBAppender">
- <connectionSource class="org.cloud.common.config.LogDBRewrite">
- </connectionSource>
- </appender>
-
- <!-- * 通配符 设置log打印级别 对所有类有效TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF-->
- <!--将上面的appender添加到root-->
- <root level="INFO">
- <!--日志输出到数据库 -->
- <appender-ref ref="DB"/>
- </root>
MyDBAppender.java
- import ch.qos.logback.classic.spi.CallerData;
- import ch.qos.logback.classic.spi.ILoggingEvent;
- import ch.qos.logback.core.db.DBAppenderBase;
- import dm.jdbc.driver.DmdbPreparedStatement;
- import dm.jdbc.driver.DmdbStatement;
- import org.slf4j.MDC;
-
- import java.lang.reflect.Method;
- import java.sql.*;
-
- /**
- * @author hhh
- * @Description lomback日志连接数据库配置
- * @create 2021-01-06
- */
- public class MyDBAppender extends DBAppenderBase<ILoggingEvent> {
-
- private String insertSQL;
- private static Method GET_GENERATED_KEYS_METHOD;
-
- private static final int LOGID_INDEX = 1;
- private static final int USERID_INDEX = 1;
- private static final int CLASS_INDEX = 2;
- private static final int MOTHOD_INDEX = 3;
- private static final int OPERATIONTIME_INDEX = 4;
- private static final int LOGLEVEL_INDEX = 5;
- private static final int MSG_INDEX = 6;
- private static final int IP_INDEX = 7;
-
-
- private static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();
-
- static {
- // PreparedStatement.getGeneratedKeys() method was added in JDK 1.4
- Method getGeneratedKeysMethod;
- try {
- getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
- } catch (Exception ex) {
- getGeneratedKeysMethod = null;
- }
- GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
- }
-
- @Override
- public void start() {
- insertSQL = buildInsertSQL();
- super.start();
- }
-
- private static String buildInsertSQL() {
- //我用的是达梦数据库,test_sequence.nextval是一个自增长的值
- return "INSERT INTO test " +
- "(event_id, USERID, CLASS, MOTHOD, OPERATIONTIME, LOGLEVEL, MSG, IP)"+
- "VALUES (test_sequence.nextval, ?, ? ,?, ?, ?, ?, ?)";
- }
-
- private void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
- stmt.setString(USERID_INDEX, event.getMDCPropertyMap().get("username"));
- stmt.setString(IP_INDEX, event.getMDCPropertyMap().get("ip"));
- stmt.setTimestamp(OPERATIONTIME_INDEX, new Timestamp(event.getTimeStamp()));
- stmt.setString(MSG_INDEX, event.getFormattedMessage());
- stmt.setString(LOGLEVEL_INDEX, event.getLevel().toString());
- stmt.setString(CLASS_INDEX, event.getLoggerName());
- stmt.setString(MOTHOD_INDEX, event.getThreadName());
- }
-
- private void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {
- }
-
- @Override
- protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {
- bindLoggingEventWithInsertStatement(insertStatement, event);
- // This is expensive... should we do it every time?
- bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());
- int updateCount = insertStatement.executeUpdate();
- if (updateCount != 1) {
- addWarn("Failed to insert loggingEvent");
- }
- }
-
- private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {
- StackTraceElement caller = EMPTY_CALLER_DATA;
- if (hasAtLeastOneNonNullElement(callerDataArray))
- caller = callerDataArray[0];
- return caller;
- }
-
- private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {
- return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;
- }
-
- @Override
- protected Method getGeneratedKeysMethod() {
- return GET_GENERATED_KEYS_METHOD;
- }
-
- @Override
- protected String getInsertSQL() {
- return insertSQL;
- }
-
- protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId){
- }
- }
LogDBRewrite.java
- import ch.qos.logback.core.db.DataSourceConnectionSource;
- import org.cloud.bdma.common.util.CryptUtil;
-
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.UnsupportedEncodingException;
- import java.sql.Connection;
- import java.sql.DriverManager;
- import java.sql.SQLException;
- import java.util.Properties;
-
- /**
- * @author zjt
- * @description logback日志数据库配置
- * @updateTime 2023/1/3 14:51
- */
- public class LogDBRewrite extends DataSourceConnectionSource {
-
- private static String url;
- private static String username;
- private static String password;
- private static String driverClassName = dm.jdbc.driver.DmDriver;
-
- @Override
- public void start() {
- try {
- if (driverClassName != null) {
- Class.forName(driverClassName);
- discoverConnectionProperties();
- } else {
- addError("WARNING: No JDBC driver specified for logback DriverManagerConnectionSource.");
- }
- } catch (final ClassNotFoundException cnfe) {
- addError("Could not load JDBC driver class: " + driverClassName, cnfe);
- }
- }
-
- @Override
- public Connection getConnection() throws SQLException {
- return DriverManager.getConnection(url, username, password);
- }
- }
数据库建表语句:(必须有一个主键)
- CREATE TABLE test(
- "event_id" int PRIMARY KEY,
- "userid" VARCHAR2(12),
- "class" VARCHAR2(255),
- "mothod" VARCHAR2(255),
- "operationtime" timestamp,
- "loglevel" VARCHAR2(255),
- "msg" VARCHAR2(555),
- "ip" VARCHAR2(50)
- );
- CREATE SEQUENCE test_sequence INCREMENT BY 1;
测试类:
-
- import lombok.extern.slf4j.Slf4j;
- import org.junit.Test;
- import org.slf4j.MDC;
- import org.springframework.boot.test.context.SpringBootTest;
-
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * @ClassName MyDBAppenderTest
- * @authtr zjt
- * @Description TODO
- * @createTime 2023年01月06日 16:03:00
- */
- @SpringBootTest
- @Slf4j
- public class MyDBAppenderTest {
-
- @Test
- public void testDB(){
- //自定义插入数据库的数据,在 MyDBAppender#bindLoggingEventWithInsertStatement 方法中的event.getMDCPropertyMap().get("ip")和event.getMDCPropertyMap().get("username") 中可以获取到这些值
- Map context = new HashMap();
- context.put("username", "test");
- context.put("ip", "127.0.0.1");
- MDC.setContextMap(context);
- log.info("测试");
- }
- }
执行后查询数据库,即可看到测试数据结果
去看 DBAppenderBase 的启动类,可以看到这行代码
表示如果不支持主键又没有方言的数据库, 那么就不支持连接到数据库写日志,而达梦lomback不支持达梦的方言,所以只能走主键这条路了。
如上图,因为达梦没有方言,所以如果 cnxSupportsGetGeneratedKeys 也为false,那么就会直接报错导致无法启动,所以需要让这个值为true,那么肯定要让这个条件为true
if (this.getGeneratedKeysMethod() != null)
所以重写这个方法,让这段代码成功执行,这样项目就可以启动啦 !
至于这个方法干嘛的,后面也有介绍,如果不深究原理就不用看下去啦。
继承了 DBAppenderBase 这个类,让我们可以重写logback写入数据库的规则:
1.LOGID_INDEX = 1 这些是为了方便修改定义出来的,可以随意修改,主要对应的是 buildInsertSQL()方法中的占位符(例如第一个?,就代表1)
2. bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) 这个方法是绑定sql中的 ? 对应的参数的
3. bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) :与存储过程相关,可以不实现
4.subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) :调用 2和3 然后对数据库进行插入(insertStatement.executeUpdate())
这里插入成功后会继续往下跑:
执行完 subAppend 后,还会继续执行 selectEventId(PreparedStatement insertStatement, Connection connection) 这个方法,继续深究:
看红框中的代码,这是反射的调用,insertStatement这个参数打个断点就能知道它的真正类型是 DmdbPreparedStatement,回到 我们定义的 MyDBAppender:getGeneratedKeysMethod() 中可以看到返回了这个Method对象 GET_GENERATED_KEYS_METHOD ,而在静态代码块中我们对它进行了初始化:
PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null)
所以这里的rs是调用了这个方法: DmdbPreparedStatement:getGeneratedKeys,再点进去可以看到实际调用的是这个方法: do_getGenerateKeys(),如下图
看红框,我就是在这里栽倒了,这里需要你的日志表里面需要有主键,而我没有定义主键,但报错的话会报空指针异常,所以在数据库增加一个主键即可!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。