Java 8 で使用可能です。Java 7 以前には対応していません。また Java Persistence API (JPA) との互換性はありません。
特徴
- Java 8 で追加された機能(関数型インタフェース、
Optional
クラス) を使用した API 。 - Java Runtime と JDBC ドライバー以外に依存するライブラリがないため、バッチ処理にも使用しやすい。
- XML ファイル等によるマッピング定義ファイルは不要。
- 大規模なライブラリではないため、学習が比較的容易。
ダウンロード
https://github.com/MasatoKokubo/Lightsleep/releases
Gradle
記述例:
apply plugin: 'java'
repositories {
jcenter()
}
dependencies {
compile 'org.lightsleep:lightsleep:1.+'
}
ロギング・ライブラリ、JDBC ドライバー、コネクション・プール・ライブラリを追加した場合の記述例:
apply plugin: 'java'
repositories {
jcenter()
}
dependencies {
compile 'org.lightsleep:lightsleep:1.+'
// ロギング・ライブラリを使用する場合
runtime 'log4j:log4j:1.+' // Log4j
runtime 'org.apache.logging.log4j:log4j-core:2.+' // Log4j2
runtime 'org.slf4j:slf4j-api:1.+' // SL4J
// 以下のいずれかまたは他の JDBC ドライバー
runtime 'mysql:mysql-connector-java:5.+' // MySQL
runtime 'com.hynnet:oracle-driver-ojdbc:12.+' // Oracle
runtime 'org.postgresql:postgresql:9.+' // PostgreSQL
runtime 'com.microsoft.sqlserver:sqljdbc4:4.+' // SQLServer
// コネクション・プール・ライブラリを使用する場合
runtime 'com.mchange:c3p0:0.+' // C3p0
runtime 'org.apache.commons:commons-dbcp2:2.+' // Dbcp
runtime 'com.zaxxer:HikariCP:2.+' // HikariCP
runtime 'org.apache.tomcat:tomcat-jdbc:8.+' // TomcatCP
}
使用方法
1. エンティティ・クラスの作成
テータベースの各テーブルに対応するエンティティ・クラス (データの入れ物) を定義します。
定義例:
package org.lightsleep.example.article.entity;
import java.sql.Date;
import org.lightsleep.entity.Key;
public class Contact {
@Key
public int id;
public String familyName;
public String givenName;
public Date birthday;
}
package org.lightsleep.example.article.entity;
import org.lightsleep.entity.Key;
public class Phone {
@Key
public int contactId;
public short childIndex;
public String label;
public String content;
}
上記例では、public
フィールドを定義していますが、private
フィールドと public getter/setter
メソッドの組み合わせても OK です。getter
メソッドは、get
プレフィックス以外に is
またはプレフィックスなしでも可能です。また setter
メソッドは、set
プレフィックス付きまたはプレフィックスなしに対応しています。
private int id;
public int getId() {return id;}
// or
public int id() {return id;}
public void setId(int id) {this.id = id;}
// or
public void id(int id) {this.id = id;}
クラス名と関連するテーブル名が異なる場合は、@Table
をクラスに付けます。
import org.lightsleep.entity.Table;
@Table("CONTACTS")
public class Contact {
プライマリ・キーのカラムに対応するフィールドには @Key
を付けます(複数可)。
カラム名とフィールド名が異なる場合は、@Column
をフィールドに付けます。
import org.lightsleep.entity.Column;
...
@Column("FAMILY_NAME")
public String familyName;
非static なフィールドは、自動的にカラムと対応付けされるため、カラムと関連しないフィールドには @NonColumn
を付けます。
import java.util.ArrayList;
import java.util.List;
import org.lightsleep.entity.NonColumn;
...
@NonColumn
public List<Phone> phones = new ArrayList<>();
SQL にフィールドの値ではなく式を指定する場合は、@Select
, @Insert
, @Update
を付けます。
また SQL に使用しない事を指定する場合は、@NonSelect
, @NonInsert
, @NonUpdate
を付けます。
import java.sql.Timestamp;
import org.lightsleep.entity.Insert;
import org.lightsleep.entity.NonUpdate;
import org.lightsleep.entity.Update;
...
@Insert("0")
@Update("{updateCount}+1")
public int updateCount;
@Insert("CURRENT_TIMESTAMP")
@NonUpdate
public Timestamp createdTime;
@Insert("CURRENT_TIMESTAMP")
@Update("CURRENT_TIMESTAMP")
public Timestamp updatedTime;
以下のアノテーションがあります。
アノテーション名 | 指定する内容 | 付与する対象 |
---|---|---|
@Table |
テーブル名 | クラス |
@Key |
プライマリ・キーに対応 | フィールド |
@Column |
カラム名 | フィールド |
@NonColumn |
カラムに関連しない | フィールド |
@NonSelect |
SELECT SQL に使用しない | フィールド |
@NonInsert |
INSERT SQL に使用しない | フィールド |
@NonUpdate |
UPDATE SQL に使用しない | フィールド |
@Select |
SELECT SQL で使用する式 | フィールド |
@Insert |
INSERT SQL で使用する式 | フィールド |
@Update |
UPDATE SQL で使用する式 | フィールド |
@KeyProperty |
プライマリ・キーに対応 | クラス |
@ColumnProperty |
カラム名 | クラス |
@NonColumnProperty |
カラムに関連しない | クラス |
@NonSelectProperty |
SELECT SQL に使用しない | クラス |
@NonInsertProperty |
INSERT SQL に使用しない | クラス |
@NonUpdateProperty |
UPDATE SQL に使用しない | クラス |
@SelectProperty |
SELECT SQL で使用する式 | クラス |
@InsertProperty |
INSERT SQL で使用する式 | クラス |
@UpdateProperty |
UPDATE SQL で使用する式 | クラス |
@XxxxxProperty は、スーパークラスで定義されているフィールドに対して指定する場合に使用します。
同一アノテーションを1つのクラスに複数付与できます。
2. lightsleep.properties の定義
定義例:
Logger = Log4j2
Database = PostgreSQL
ConnectionSupplier = Dbcp
url = jdbc:postgresql://postgresqlserver/article
username = article
password = _article_
initialSize = 10
maxTotal = 100
Logger は、ログを出力する方法の指定で、以下から選択します。
指定内容 | 使用するロギング・ライブラリ | ログ・レベル | 定義ファイル |
---|---|---|---|
Jdk |
Java Runtime | - | logging.properties に定義 |
Log4j |
Log4j 1.x.x | - | log4j.properties または log4j.xml に定義 |
Log4j2 |
Log4j 2.x.x | - | log4j2.xml に定義 |
SLF4J |
SLF4J | - | 対象とするロギング・ライブラリ実装に依存 |
Std$Out$Trace |
System.out に出力 | trace | - |
Std$Out$Debug |
同上 | debug | - |
Std$Out$Info |
同上 | info | - |
Std$Out$Warn |
同上 | warn | - |
Std$Out$Error |
同上 | error | - |
Std$Out$Fatal |
同上 | fatal | - |
Std$Err$Trace |
System.err に出力 | trace | - |
Std$Err$Debug |
同上 | debug | - |
Std$Err$Info |
同上 | info | - |
Std$Err$Warn |
同上 | warn | - |
Std$Err$Error |
同上 | error | - |
Std$Err$Fatal |
同上 | fatal | - |
指定がない場合は、Std$Out$Info
が選択されます。
Database は、対象とする DBMS の指定で、以下から選択します。
MySQL
Oracle
PostgreSQL
SQLServer
上記以外の DBMS を使用する場合は、指定しないか Standard
を指定します。
ただしその場合は、DBMS 固有の機能は使用できません。
ConnectionSupplier は、使用するコネクション・サプライヤー (コネクション・プール等) の指定で以下から選択します。
指定内容 | コネクション・サプライヤー |
---|---|
C3p0 | c3p0 |
Dbcp | Apache Commons DBCP |
HikariCP | HikariCP |
TomcatCP | Tomcat JDBC Connection Pool |
Jndi | Java Naming and Directory Interface (JNDI) (Tomcat の場合) |
Jdbc | DriverManager#getConnection(String url, Properties info) メソッド |
上記 lightsleep.properties の定義例の ConnectionSupplier より下 (url ~ maxTotal) は、各コネクション・サプライヤーに渡す定義内容です。
定義例 (Jdk / MySQL / C3p0):
Logger = Jdk
Database = MySQL
ConnectionSupplier = C3p0
url = jdbc:mysql://mysql57/article
user = article
password = _article_
url, user, password 以外の定義は、c3p0.properties または c3p0-config.xml に記述します。
定義例 (Log4j / Oracle / Dbcp):
Logger = Log4j
Database = Oracle
ConnectionSupplier = Dbcp
url = jdbc:oracle:thin:@oracle121:1521:article
username = article
password = _article_
initialSize = 10
maxTotal = 100
...
定義例 (Log4j2 / PostgreSQL / HikariCP):
Logger = Log4j2
Database = PostgreSQL
ConnectionSupplier = HikariCP
jdbcUrl = jdbc:postgresql://postgresql95/article
username = article
password = _article_
minimumIdle = 10
maximumPoolSize = 100
...
定義例 (SLF4J / SQLServer / TomcatCP):
Logger = SLF4J
Database = SQLServer
ConnectionSupplier = TomcatCP
url = jdbc:sqlserver://sqlserver13;database=article
username = article
password = _article_
initialSize = 10
maxActive = 100
...
定義例 (Log4j / MySQL / Jndi):
Logger = Log4j
Database = MySQL
ConnectionSupplier = Jndi
dataSource = jdbc/example
定義例 (Log4j2 / Oracle / Jdbc):
Logger = Log4j2
Database = Oracle
ConnectionSupplier = Jdbc
url = jdbc:oracle:thin:@oracle121:1521:article
user = article
password = _article_
3. データベース・アクセス
Transaction.execute
メソッドの実行が1つのトランザクションの実行に相当します。
トランザクションの内容は、引数 transaction
(ラムダ式) で定義します。
ラムダ式は、Transaction.executeBody
メソッドの内容に相当し、このメソッドの引数は、Connection
です。
import org.lightsleep.Sql;
import org.lightsleep.Transaction;
import org.lightsleep.example.article.entity.Contact;
...
// トランザクション定義例
Transaction.execute(connection -> {
// トランザクション内容開始
new Sql<>(Contact.class).insert(connection, contact);
...
// トランザクション内容終了
});
トランザクション中に例外がスローされた場合は、Transaction.rollback
メソッドが実行され、
そうでなければ Transaction.commit
メソッドが実行されます。
以下 Java ソースの記述例と実行される SQL (MySQL または PostgreSQL の場合) です。
3-1. SELECT
3-1-1. SELECT 1行 / 式条件
Transaction.execute(connection -> {
Optional<Contact> contactOpt = new Sql<>(Contact.class)
.where("{id}={}", 1)
.select(connection);
});
SELECT id, familyName, givenName, birthday, updateCount, createdTime, updatedTime FROM Contact WHERE id=1
3-1-2. SELECT 1行 / エンティティ条件
Contact contact = new Contact();
contact.id = 1;
Transaction.execute(connection -> {
Optional<Contact> contactOpt = new Sql<>(Contact.class)
.where(contact)
.select(connection);
});
SELECT id, familyName, givenName, birthday, updateCount, createdTime, updatedTime FROM Contact WHERE id=1
3-1-3. SELECT 複数行 / 式条件
List<Contact> contacts = new ArrayList<Contact>();
Transaction.execute(connection ->
new Sql<>(Contact.class)
.where("{familyName}={}", "Apple")
.select(connection, contacts::add)
);
SELECT id, familyName, givenName, birthday, updateCount, createdTime, updatedTime FROM Contact WHERE familyName='Apple'
3-1-4. SELECT サブクエリ条件
List<Contact> contacts = new ArrayList<Contact>();
Transaction.execute(connection ->
new Sql<>(Contact.class, "C")
.where("EXISTS",
new Sql<>(Phone.class, "P")
.where("{P.contactId}={C.id}")
)
.select(connection, contacts::add)
);
SELECT C.id AS C_id, C.familyName AS C_familyName, C.givenName AS C_givenName, C.birthday AS C_birthday, C.updateCount AS C_updateCount, C.createdTime AS C_createdTime, C.updatedTime AS C_updatedTime FROM Contact C WHERE EXISTS (SELECT * FROM Phone P WHERE P.contactId=C.id)
3-1-5. SELECT 式条件 / AND
List<Contact> contacts = new ArrayList<Contact>();
Transaction.execute(connection ->
new Sql<>(Contact.class)
.where("{familyName}={}", "Apple")
.and ("{givenName}={}", "Akane")
.select(connection, contacts::add)
);
SELECT id, familyName, givenName, birthday, updateCount, createdTime, updatedTime FROM Contact WHERE (familyName='Apple' AND givenName='Akane')
3-1-6. SELECT 式条件 / OR
List<Contact> contacts = new ArrayList<Contact>();
Transaction.execute(connection ->
new Sql<>(Contact.class)
.where("{familyName}={}", "Apple")
.or ("{familyName}={}", "Orange")
.select(connection, contacts::add)
);
SELECT id, familyName, givenName, birthday, updateCount, createdTime, updatedTime FROM Contact WHERE (familyName='Apple' OR familyName='Orange')
3-1-7. SELECT 式条件 / (A AND B) OR (C AND D)
List<Contact> contacts = new ArrayList<Contact>();
Transaction.execute(connection ->
new Sql<>(Contact.class)
.where(Condition
.of ("{familyName}={}", "Apple")
.and("{givenName}={}", "Akane")
)
.or(Condition
.of ("{familyName}={}", "Orange")
.and("{givenName}={}", "Setoka")
)
.select(connection, contacts::add)
);
SELECT id, familyName, givenName, birthday, updateCount, createdTime, updatedTime FROM Contact WHERE ((familyName='Apple' AND givenName='Akane') OR (familyName='Orange' AND givenName='Setoka'))
3-1-8. SELECT カラムの選択
List<Contact> contacts = new ArrayList<Contact>();
Transaction.execute(connection ->
new Sql<>(Contact.class)
.where("{familyName}={}", "Apple")
.columns("familyName", "givenName")
.select(connection, contacts::add)
);
SELECT familyName, givenName FROM Contact WHERE familyName='Apple'
3-1-9. SELECT GROUP BY, HAVING
List<Contact> contacts = new ArrayList<Contact>();
Transaction.execute(connection ->
new Sql<>(Contact.class, "C")
.columns("familyName")
.groupBy("{familyName}")
.having("COUNT({familyName})>=2")
.select(connection, contacts::add)
);
SELECT MIN(C.familyName) AS C_familyName FROM Contact C GROUP BY C.familyName HAVING COUNT(C.familyName)>=2
3-1-10. SELECT ORDER BY, OFFSET, LIMIT
List<Contact> contacts = new ArrayList<Contact>();
Transaction.execute(connection ->
new Sql<>(Contact.class)
.orderBy("{familyName}")
.orderBy("{givenName}")
.orderBy("{id}")
.offset(10).limit(5)
.select(connection, contacts::add)
);
SELECT id, familyName, givenName, birthday, updateCount, createdTime, updatedTime FROM Contact ORDER BY familyName ASC, givenName ASC, id ASC LIMIT 5 OFFSET 10
3-1-11. SELECT FOR UPDATE
Transaction.execute(connection -> {
Optional<Contact> contactOpt = new Sql<>(Contact.class)
.where("{id}={}", 1)
.forUpdate()
.select(connection);
});
SELECT id, familyName, givenName, birthday, updateCount, createdTime, updatedTime FROM Contact WHERE id=1 FOR UPDATE
3-1-12. SELECT 内部結合
List<Contact> contacts = new ArrayList<>();
List<Phone> phones = new ArrayList<>();
Transaction.execute(connection ->
new Sql<>(Contact.class, "C")
.innerJoin(Phone.class, "P", "{P.contactId}={C.id}")
.where("{C.id}={}", 1)
.<Phone>select(connection, contacts::add, phones::add)
);
SELECT C.id AS C_id, C.familyName AS C_familyName, C.givenName AS C_givenName, C.birthday AS C_birthday, C.updateCount AS C_updateCount, C.createdTime AS C_createdTime, C.updatedTime AS C_updatedTime, P.contactId AS P_contactId, P.childIndex AS P_childIndex, P.label AS P_label, P.content AS P_content FROM Contact C INNER JOIN Phone P ON P.contactId=C.id WHERE C.id=1
3-1-13. SELECT 左外部結合
List<Contact> contacts = new ArrayList<>();
List<Phone> phones = new ArrayList<>();
Transaction.execute(connection ->
new Sql<>(Contact.class, "C")
.leftJoin(Phone.class, "P", "{P.contactId}={C.id}")
.where("{C.familyName}={}", "Apple")
.<Phone>select(connection, contacts::add, phones::add)
);
SELECT C.id AS C_id, C.familyName AS C_familyName, C.givenName AS C_givenName, C.birthday AS C_birthday, C.updateCount AS C_updateCount, C.createdTime AS C_createdTime, C.updatedTime AS C_updatedTime, P.contactId AS P_contactId, P.childIndex AS P_childIndex, P.label AS P_label, P.content AS P_content FROM Contact C LEFT OUTER JOIN Phone P ON P.contactId=C.id WHERE C.familyName='Apple'
3-1-14. SELECT 右外部結合
List<Contact> contacts = new ArrayList<>();
List<Phone> phones = new ArrayList<>();
Transaction.execute(connection ->
new Sql<>(Contact.class, "C")
.rightJoin(Phone.class, "P", "{P.contactId}={C.id}")
.where("{P.label}={}", "Main")
.<Phone>select(connection, contacts::add, phones::add)
);
SELECT C.id AS C_id, C.familyName AS C_familyName, C.givenName AS C_givenName, C.birthday AS C_birthday, C.updateCount AS C_updateCount, C.createdTime AS C_createdTime, C.updatedTime AS C_updatedTime, P.contactId AS P_contactId, P.childIndex AS P_childIndex, P.label AS P_label, P.content AS P_content FROM Contact C RIGHT OUTER JOIN Phone P ON P.contactId=C.id WHERE P.label='Main'
3-2. INSERT
3-2-1. INSERT 1行
Contact contact = new Contact();
contact.id = 1;
contact.familyName = "Apple";
contact.givenName = "Akane";
Calendar calendar = Calendar.getInstance();
calendar.set(2001, 1-1, 1, 0, 0, 0);
contact.birthday = new Date(calendar.getTimeInMillis())
Transaction.execute(connection -> {
new Sql<>(Contact.class).insert(connection, contact));
INSERT INTO Contact (id, familyName, givenName, birthday, updateCount, createdTime, updatedTime) VALUES (1, 'Apple', 'Akane', DATE'2001-01-01', 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
3-2-2. INSERT 複数行
List<Contact> contacts = new ArrayList<>();
Contact contact = new Contact();
contact.id = 2; contact.familyName = "Apple"; contact.givenName = "Yukari";
Calendar calendar = Calendar.getInstance();
calendar.set(2001, 1-1, 2, 0, 0, 0);
contact.birthday = new Date(calendar.getTimeInMillis());
contacts.add(contact);
contact = new Contact();
contact.id = 3; contact.familyName = "Apple"; contact.givenName = "Azusa";
calendar = Calendar.getInstance();
calendar.set(2001, 1-1, 3, 0, 0, 0);
contact.birthday = new Date(calendar.getTimeInMillis());
contacts.add(contact);
Transaction.execute(connection ->
new Sql<>(Contact.class).insert(connection, contacts));
INSERT INTO Contact (id, familyName, givenName, birthday, updateCount, createdTime, updatedTime) VALUES (2, 'Apple', 'Yukari', DATE'2001-01-02', 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
INSERT INTO Contact (id, familyName, givenName, birthday, updateCount, createdTime, updatedTime) VALUES (3, 'Apple', 'Azusa', DATE'2001-01-03', 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
3-3. UPDATE
3-3-1. UPDATE 1行
Transaction.execute(connection ->
new Sql<>(Contact.class)
.where("{id}={}", 1)
.select(connection)
.ifPresent(contact -> {
contact.givenName = "Akiyo";
new Sql<>(Contact.class).update(connection, contact);
})
);
SELECT id, familyName, givenName, birthday, updateCount, createdTime, updatedTime FROM Contact WHERE id=1
UPDATE Contact SET familyName='Apple', givenName='Akiyo', birthday=DATE'2001-01-01', updateCount=updateCount+1, updatedTime=CURRENT_TIMESTAMP WHERE id=1
3-3-2. UPDATE 複数行
Transaction.execute(connection -> {
List<Contact> contacts = new ArrayList<>();
new Sql<>(Contact.class)
.where("{familyName}={}", "Apple")
.select(connection, contact -> {
contact.familyName = "Apfel";
contacts.add(contact);
});
new Sql<>(Contact.class).update(connection, contacts);
});
SELECT id, familyName, givenName, birthday, updateCount, createdTime, updatedTime FROM Contact WHERE familyName='Apple'
UPDATE Contact SET familyName='Apfel', givenName='Akiyo', birthday=DATE'2001-01-01', updateCount=updateCount+1, updatedTime=CURRENT_TIMESTAMP WHERE id=1
UPDATE Contact SET familyName='Apfel', givenName='Yukari', birthday=DATE'2001-01-02', updateCount=updateCount+1, updatedTime=CURRENT_TIMESTAMP WHERE id=2
UPDATE Contact SET familyName='Apfel', givenName='Azusa', birthday=DATE'2001-01-03', updateCount=updateCount+1, updatedTime=CURRENT_TIMESTAMP WHERE id=3
3-3-3. UPDATE 指定条件, カラム選択
Contact contact = new Contact();
contact.familyName = "Pomme";
Transaction.execute(connection ->
new Sql<>(Contact.class)
.where("{familyName}={}", "Apfel")
.columns("familyName")
.update(connection, contact)
);
UPDATE Contact SET familyName='Pomme' WHERE familyName='Apfel'
3-3-4. UPDATE 全行
Contact contact = new Contact();
Transaction.execute(connection ->
new Sql<>(Contact.class)
.where(Condition.ALL)
.columns("birthday")
.update(connection, contact)
);
UPDATE Contact SET birthday=NULL
3-4. DELETE
3-4-1. DELETE 1行
Transaction.execute(connection ->
new Sql<>(Contact.class)
.where("{id}={}", 1)
.select(connection)
.ifPresent(contact ->
new Sql<>(Contact.class).delete(connection, contact))
);
SELECT id, familyName, givenName, birthday, updateCount, createdTime, updatedTime FROM Contact WHERE id=1
DELETE FROM Contact WHERE id=1
3-4-2. DELETE 複数行
Transaction.execute(connection -> {
List<Contact> contacts = new ArrayList<>();
new Sql<>(Contact.class)
.where("{familyName}={}", "Pomme")
.select(connection, contacts::add);
new Sql<>(Contact.class).delete(connection, contacts);
});
SELECT id, familyName, givenName, birthday, updateCount, createdTime, updatedTime FROM Contact WHERE familyName='Pomme'
DELETE FROM Contact WHERE id=2
DELETE FROM Contact WHERE id=3
3-4-3. DELETE 指定条件
Transaction.execute(connection -> {
List<Contact> contacts = new ArrayList<>();
new Sql<>(Contact.class)
.where("{familyName}={}", "Pomme")
.select(connection, contacts::add);
new Sql<>(Contact.class).delete(connection, contacts);
});
DELETE FROM Contact WHERE familyName='Orange'
3-4-4. DELETE 全行
Transaction.execute(connection ->
new Sql<>(Phone.class).where(Condition.ALL).delete(connection));
DELETE FROM Phone