DBInterface
and DBQuery
along with DBAnnotationProcessor
, which does all the work. Using DBInterface
, an interface with database DBQuery
methods is marked, and DBQuery
marks the methods themselves. Methods can have parameters that will be used in a SQL query. For example: @DBInterface(url = SpendDB.URL, login = SpendDB.USER_NAME, password = SpendDB.PASSWORD) @DBInterfaceRx public interface SpendDB { String USER_NAME = "postgres"; String PASSWORD = "1234"; String URL = "jdbc:postgresql://192.168.1.26:5432/spend"; @DBMakeRx(modelClassName = "com.qwert2603.retrobase_example.DataBaseRecord") @DBQuery("SELECT * from spend_test") ResultSet getAllRecords(); @DBMakeRx @DBQuery("DELETE FROM spend_test WHERE id = ?") void deleteRecord(int id) throws SQLException; }
DBAnnotationProcessor
, where the generation of the class that implements the interface is carried out, the generated class will have the name *_* + Impl
interface_name *_* + Impl
: TypeSpec.Builder newTypeBuilder = TypeSpec.classBuilder(dbInterfaceClass.getSimpleName() + GENERATED_FILENAME_SUFFIX) .addSuperinterface(TypeName.get(dbInterfaceClass.asType())) .addField(mConnection) .addMethod(waitInit) .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
FieldSpec mConnection = FieldSpec.builder(Connection.class, "mConnection", Modifier.PRIVATE) .initializer("null") .build();
PreparedStatement
is also created for each request: FieldSpec fieldSpec = FieldSpec .builder(PreparedStatement.class, executableElement.getSimpleName().toString() + PREPARED_STATEMENT_SUFFIX) .addModifiers(Modifier.PRIVATE) .initializer("null") .build();
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(executableElement.getSimpleName().toString()) .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .returns(returnTypeName);
void
if the SQL query is an INSERT, DELETE, or UPDATE. Or ResultSet
, if the SQL query is a SELECT.SQLException
. If it can, they will be dropped from the implementation of the method. And if not - caught and displayed in stderr
.PreparedStatement
: insertRecord_PreparedStatement.setString(1, kind);
DBQuery
annotation. JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(filename); Writer writer = sourceFile.openWriter(); writer.write(javaFile.toString());
ResultSet
, defining only the interface. And it would be even more convenient to take advantage of the popular RxJava and get Observable
. In addition, it will make it easy to solve the problem with the execution of queries in another thread.DBMakeRxAnnotationProcessor
was created along with DBInterfaceRx
and DBMakeRx
, which allow you to create a class with wrapper methods. You could already see the application of these annotations in the example above. The created class will have the name *_* + Rx
interface_name *_* + Rx
, and will also have an open constructor that accepts an interface object annotated with DBInterfaceRx
, to which it will redirect requests, returning results in a reactive style.DBMakeRx
annotation to the method and pass the model class name to it. The generated wrapper method will return an Observable<* *>
. In this case, the class name of the model can not be defined. In this case, the generated method will return io.reactivex.Completable
, which is convenient for INSERT, DELETE or UPDATE SQL queries that do not require returning a result.ResultSet getAllRecords();
interface ResultSet getAllRecords();
and void deleteRecord(int id) throws SQLException;
From the example above, the following wrapper methods will be generated: public io.reactivex.Observable<com.qwert2603.retrobase_example.DataBaseRecord> getAllRecords() { return Observable.generate(() -> mDB.getAllRecords(), (resultSet, objectEmitter) -> { if (resultSet.next()) { objectEmitter.onNext(new com.qwert2603.retrobase_example.DataBaseRecord(resultSet)); } else { objectEmitter.onComplete(); } } , ResultSet::close); } public Completable deleteRecord(int id) { return Completable.fromAction(() -> mDB.deleteRecord(id)); }
mDB
is an interface object annotated by DBInterfaceRx
that was passed to the constructor.ResultSet
, so the model class must have an open constructor that accepts ResultSet
. public Completable insertRecord(String kind, int value, Date date) { ... mDB.insertRecord(kind, value, date); ... }
private SpendDB mSpendDB = new SpendDBImpl(); private SpendDBRx mSpendDBRx = new SpendDBRx(mSpendDB); public Single<List<DataBaseRecord>> getAllRecords() { return mSpendDBRx.getAllRecords() .toList() .compose(applySchedulers()); }
new SpendDBImpl();
or new SpendDBRx(mSpendDB);
to perform tests, you can use the popular Dagger .returning id
construct to the end of the SQL query and get the ResultSet
with the required id @DBMakeRx(modelClassName = "com.qwert2603.spenddemo.model.Id") @DBQuery("UPDATE spend_test SET kind=?, value=?, date=? WHERE id=? returning id") ResultSet updateRecord(String kind, int value, Date date, int id) throws SQLException;
DBMakeRx
annotation to get Observable<*id*>
. For you need to create a model class that gets the Id from the ResultSet
and pass it to the modelClassName
parameter of the modelClassName
annotation. The class of the model containing Id may look as follows: public class Id { private int mId; public Id(ResultSet resultSet) throws SQLException { mId = resultSet.getInt(1); } public int getId() { return mId; } public void setId(int id) { mId = id; } }
java.sql.Connection#isValid(0)
added to automatically create a new connection to the database in case of an error (for example, the connection is lost).Source: https://habr.com/ru/post/311716/
All Articles