MYSQL
HOME > DB > MYSQL
2018.09.30 / 22:32

Execute Java code from MySQL Trigger

GAScripter
추천 수 10

The other day I was looking to trigger some Java code from a MySQL Trigger. The reason for this was I am writing a service that should monitor events from multiple other server components that all already have a database connection. So instead of opening yet another socket for this service, why not use the existing database engine? Our events are stored in the database anyway and the server cannot operate when the database is down (so I would not be introducing any extra dependencies). Reading around on the internet however had me believe that executing external code from a MySQL trigger isn't possible. 

Yet the more comments I read from people saying this was not possible the more I was refusing to believe just that :-) Instead it got me thinking.. maybe there was some SQL statement that allows for some kind of interoperability? Quickly I was focusing my thoughts around the SELECT ... INTO OUTFILE statement. My idea was to either load some dummy data into a temporary file and use OS file/dir monitoring events to be informed about this, or writing to a named pipe so to trigger the other end of the pipe (which would be my Java code). The idea of having physical disk access every time a trigger gets fired didn't strike me as very good so I decided to go for the named pipes.

I started out coding for Windows because that's the primary target platform for the service I am working on. I wrote a JNI library to create a named pipe and wait for any action on that pipe asynchronously. Once any action happens the library will attach back to the JVM - thanks to Adam by the way for his info on asynchronous callbacks using JNI - and call the previously registered Java class/method. Rather against my expectations this immediately gave the desired result when manually firing the SELECT ... INTO OUTFILE statement passing the named pipe as outfile. The JNI end of the named pipe got triggered! As an unfortunate side effect however I also received an exception in MySQL. It seems SELECT ... INTO OUTFILE can only write to new files, not to existing files (a named pipe once created behaves as an existing file). At first I was very disappointed about this but after some further contemplation I realized that the exception doesn't matter in this case since I only required the pipe to be signalled, not any actual data to be written. And when adding the statement to an actual trigger I could declare an empty continue handler inside it to suppress the sql exception.

Now that all pieces were falling into place I created a nice Java library with an interface for registering a trigger with MySQL and receiving the trigger action in Java. The JAR includes the required DLL to create the named pipe and wait for connection on the pipe asynchronously. The DLL is included for both 32 bits and 64 bits Windows and the code will check runtime which version is required (using the "sun.arch.data.model" system property). For now the package is Windows-only but when I have some time I will make this Linux compatible as well, don't worry. update 12-06-2012: the package will now also work on Linux; note however that on Linux it works slightly different because named pipes didn't have the same effect; so instead it mounts a memory file system on a temporary directory to function as named pipe substitue; and then listens using inotify

I have not tested performance extensively but since the solution uses in-memory communication and a blocking read in a separate thread until trigger data comes in performance must be decent at least. Usage so far in my own project has comfirmed this. If someone has actual numbers please leave a comment.

Now let's get down to the code. Here is the interface you need to implement:

import nl.rocksolit.db.*;

public interface DatabaseTriggerMonitor
{
public void onTrigger(DatabaseTrigger trigger);
}


And here is how to activate a trigger:

import nl.rocksolit.db.*;

DatabaseTrigger dbTrigger = new DatabaseTrigger(conn, "EventLog", DatabaseTrigger.Time.AFTER, DatabaseTrigger.Event.INSERT, this);

..
..


and to release it when you're done:

..
..

dbTrigger.drop();


Please note that the trigger is given an automatically generated name in the form on<TableName><Time><Event> so the above trigger would be named onEventLogAfterInsert in MySQL. Any existing trigger with this name will be dropped! This is done because MySQL can only store one trigger per action time and event. Calling .drop() to release the trigger is optional but wise since it will remove the trigger in MySQL and nicely close the named pipe. If not dropped before your Java code terminates every trigger in MySQL will still result in firing the action and thus trying to write to the pipe. While this won't damage anything it is undoubtedly going to cost you performance.

limitations and known issues:
- tested on Windows 7 (x64) with MySQL 5.1.49 and CentOS 5.4 (x64) with MySQL 5.0.77
- up to 64 triggers can be registered simultaneously (ie. created without calling drop on one or more first)
- only one trigger can exist for the same table-time-event combination at the same time, creating a subsequent one will drop the previous one
- only tested with Java code running on same machine as MySQL, however under Windows it should also work if both ends are running in the same network
- the database user in effect must have the MySQL FILE privilege

This package is © 2012 by Arthur de Vaan/RocksolIT. It may be used and distributed for free as long as the included license is obeyed.
If the package saves you time and/or headaches, or if it helps you make some bucks please consider a small donation and/or note of appreciation.

jdbtrigger.jar



MySQL 트리거에서 Java 코드 실행가능! 또 다른 RocksolIT ™ 솔루션


요즘에는 MySQL 트리거에서 일부 Java 코드를 트리거하려고합니다. 그 이유는 이미 데이터베이스 연결이있는 다른 여러 서버 구성 요소의 이벤트를 모니터해야하는 서비스를 작성하기 위해서입니다. 따라서이 서비스를위한 또 다른 소켓을 여는 대신 기존 데이터베이스 엔진을 사용하지 않는 이유는 무엇입니까? 어쨌든 우리의 이벤트는 데이터베이스에 저장되며 데이터베이스가 다운되었을 때 서버가 작동하지 않습니다 (따라서 추가 종속성을 도입하지 않을 것입니다). 그러나 인터넷에서 읽는 것은 MySQL 트리거에서 외부 코드를 실행하는 것이 불가능하다고 생각했습니다.

그러나 사람들이 읽지 못했던 의견을 더 많이 읽으면 더 많은 것을 믿을 수 없게되었습니다 .--) 대신 저에게 생각이 들었습니다. 어떤 종류의 상호 운용성을 허용하는 SQL 문이있을 수도 있습니다. 나는 SELECT ... INTO OUTFILE 문에 대해 빠르게 생각을 집중하고있었습니다. 내 생각은 임시 파일에 더미 데이터를로드하고 OS 파일 / 디렉터리 모니터링 이벤트를 사용하여이 정보를 받거나 명명 된 파이프에 쓰면 파이프의 다른 쪽 끝 (내 Java 코드)이 트리거됩니다. . 방아쇠가 발사 될 때마다 물리적 인 디스크 접근을한다는 아이디어는 나에게 큰 타격을주지 않았으므로 명명 된 파이프로 가기로 결정했습니다.

Windows에서 코딩 작업을 시작한 이유는 이것이 내가 작업중인 서비스의 주요 대상 플랫폼이기 때문입니다. JNI 라이브러리를 작성하여 명명 된 파이프를 작성하고 해당 파이프의 모든 조치를 비동기 적으로 기다립니다. 액션이 발생하면 JNI를 사용하여 비동기 콜백에 대한 정보를 얻 습니다. Adam 덕택에 라이브러리가 JVM에 다시 첨부됩니다.- 이전에 등록 된 Java 클래스 / 메소드를 호출하십시오. 오히려 내 기대에 대해 이것은 SELECT ... INTO OUTFILE 문을 수동으로 실행하여 명명 된 파이프를 outfile로 전달할 때 원하는 결과를 즉시 나타냅니다. 명명 된 파이프의 JNI 끝이 트리거되었습니다! 불행한 부작용으로 그러나 나는 또한 MySQL에있는 예외를 받았다. SELECT ... INTO OUTFILE은 기존 파일이 아닌 새 파일에만 쓸 수 있습니다 (한 번 생성 된 명명 된 파이프는 기존 파일로 작동합니다). 처음에 나는 이것에 대해 매우 실망했다. 그러나 몇 가지 더 숙고 한 후에 필자는 실제 데이터가 쓰여지는 것이 아니라 신호가 보내 지기만하면되므로 예외가 중요하지 않다는 것을 깨달았다. 그리고 실제 트리거에 문을 추가 할 때 SQL 예외를 억제하기 위해 빈 내부에 계속 처리기를 선언 할 수 있습니다.

이제 모든 부분이 떨어지면서 MySQL을 사용하여 트리거를 등록하고 Java에서 트리거 조치를 수신 할 수있는 인터페이스가있는 멋진 Java 라이브러리를 만들었습니다. JAR에는 명명 된 파이프를 작성하고 비동기 적으로 파이프 연결을 기다리는 데 필요한 DLL이 들어 있습니다. DLL은 32 비트 및 64 비트 Windows 모두에 포함되며 코드는 런타임에 "sun.arch.data.model"시스템 등록 정보를 사용하여 필요한 버전을 확인합니다. 지금은 패키지가 Windows 전용이지만 시간이 좀 있어도이 Linux를 호환 가능하게 만들 것이므로 걱정하지 마십시오.12-06-2012 업데이트 : 패키지가 이제 Linux에서도 작동합니다. 그러나 리눅스에서는 명명 된 파이프가 같은 효과를 가지지 않기 때문에 약간 다르게 작동합니다; 대신 임시 파일 디렉토리에 메모리 파일 시스템을 마운트하여 명명 된 파이프 대체 파일로 작동합니다. 그런 다음 inotify를 사용하여 청취 

성능을 광범위하게 테스트하지는 않았지만 솔루션이 메모리 내 통신을 사용하고 트리거 데이터가 성능에 제공 될 때까지 별도의 스레드에서 블로킹을 읽으므로 성능이 최소한으로 유지되어야합니다. 지금까지 내 프로젝트에서 사용법이 이것을 확인했다. 누군가 실제 전화 번호가 있으면 의견을 남겨주세요. 

이제 코드를 살펴 보겠습니다. 다음은 구현해야하는 인터페이스입니다.

import nl.rocksolit.db. *; 

공용 인터페이스 DatabaseTriggerMonitor 

public void onTrigger (DatabaseTrigger trigger); 
}


다음은 트리거를 활성화하는 방법입니다.

import nl.rocksolit.db. *; 

DatabaseTrigger dbTrigger = 새 DatabaseTrigger (conn, "EventLog", DatabaseTrigger.Time.AFTER, DatabaseTrigger.Event.INSERT, this); 

.. 
..


끝나면 그것을 놓을 수 있습니다 :

.. 
.. 

dbTrigger.drop ();


트리거는 <TableName> <Time> <Event>의 형식으로 자동 생성 된 이름이 주어 지므로 위의 트리거는 MySQL의 onEventLogAfterInsert라는 이름이됩니다. 이 이름을 가진 기존 트리거가 삭제됩니다! 이것은 MySQL이 작업 시간과 이벤트 당 하나의 트리거 만 저장할 수 있기 때문에 수행됩니다. 트리거를 해제하기 위해 .drop ()를 호출하는 것은 선택 사항이지만 MySQL에서 트리거를 제거하고 명명 된 파이프를 잘 닫을 것이므로 현명합니다. Java 코드가 종료되기 전에 삭제되지 않으면 MySQL의 모든 트리거가 여전히 작업을 시작하여 파이프에 쓰려고합니다. 이렇게해도 아무 것도 손상되지는 않지만 의심 할 여지없이 성능에 영향을줍니다. 

제한 및 알려진 문제점 : 
- Windows 7 (x64)에서 MySQL 5.1.49 및 CentOS 5.4 (x64)에서 MySQL 5.0.77을 사용하여 테스트
- 최대 64 개의 트리거를 동시에 등록 할 수 있습니다 (즉, 
하나 이상의 트리거를 먼저 호출하지 않고 생성) . 동일한 테이블 시간 이벤트 조합에 대해 동시에 하나의 트리거 만 존재할 수 있습니다. 이후의 트리거를 작성하면 이전의 트리거가 삭제됩니다 하나 
는 MySQL과 동일한 컴퓨터에서 실행되는 Java 코드로만 테스트되지만 Windows에서는 양쪽 끝이 동일한 네트워크에서 실행되는 경우에도 작동 
해야합니다. 실제로 데이터베이스 사용자는 MySQL FILE 권한 

이 있어야합니다. 이 패키지는 Arthur de Vaan / RocksolIT. 포함 된 라이센스를 준수하는 한 무료로 사용 및 배포 할 수 있습니다. 
패키지가 시간 및 / 또는 두통을 덜어 주었거나 도움이된다면 작은 기부금 및 / 또는 감사장을 생각해보십시오.

jdbtrigger.jar