Activate Persistence Framework
O Activate é um framework para persistência de objetos em Scala. Ele é um STM (Software Transacional Memory) durável, com persistência plugável. Seu núcleo é o RadonSTM, que provê um poderoso o mecanismo de controle de transações em memória, análogo às transações dos bancos de dados, para controle de concorrência otimista. A durabilidade das transações (persistência) é plugável, sendo possível utilizar persistência em diferentes paradigmas como relacional (JDBC), prevalência (Prevayler) e não relacional (MongoDB).
Os principais benefícios do framework são:
- Transações atômicas, consistentes, isoladas e duráveis. É possível utilizar as entidades sem se preocupar com problemas de concorrência.
- As entidades sempre estão consistentes em memória e na camada de persistência. Por exemplo, ao ocorrer rollback, as entidades em memória não ficam em um estado inconsistente.
- Controle de propagação das transações, incluindo transações aninhadas.
- Persistência transparente. Basta usar as entidades dentro de transações e elas são automaticamente persistidas.
- As entidades são carregadas de forma lazy e inicializadas automaticamente quando necessário.
- As consultas são type-safe e consistentes, inclusive com os objetos criados na transação corrente. Portanto, uma entidade criada na mesma transação pode ser retornada em uma consulta.
Baixe o xsbt sbt-launch.jar e o activate example project.
Crie um script para o chamar o sbt como esse e coloque no path do sistema:
java -XX:MaxPermSize=512m -Xmx512M -noverify -jar /location/of/sbt-launch.jar "$@"
Lembre-se de substituir "/location/of/sbt-launch.jar" com o caminho do sbt-launch.jar baixado. Você pode também chamar essa linha de comando diretamente. Lembre-se de adicionar a opção "-noverify"
Modifique project/ActivateExampleBuild.scala e com/example/foo/ActivateExampleContext.scala para determinar o storage. Memory storage é o padrão.
Chame o sbt dentro da pasta activate-example e crie o projeto eclipse:
$ sbt
> eclipse
Agora você pode importar o projeto no eclipse. É necessário que o plugin do scala esteja instalado http://scala-ide.org/.
Caso deseje alterar o storage, basta alterar as duas classes citadas acima, abrir o console sbt e recriar o projeto eclipse com o mesmo comando ("eclipse").
Inicialmente, deve ser criado o contexto do Activate. O contexto deve ser um singleton, portanto faz sentido declarar como "object":
Prevayler
import net.fwbrasil.activate.ActivateContext
import net.fwbrasil.activate.storage.prevayler.PrevaylerMemoryStorage
object prevaylerContext extends ActivateContext {
def contextName = "prevaylerContext"
val storage = new PrevaylerMemoryStorage
}
Memória transiente
import net.fwbrasil.activate.ActivateContext
import net.fwbrasil.activate.storage.memory.MemoryStorage
object memoryContext extends ActivateContext {
def contextName = "memoryContext"
val storage = new MemoryStorage
}
Oracle
import net.fwbrasil.activate.ActivateContext
import net.fwbrasil.activate.storage.relational.JdbcRelationalStorage
import net.fwbrasil.activate.storage.relational.oracleDialect
object oracleContext extends ActivateContext {
def contextName = "oracleContext"
val storage = new SimpleJdbcRelationalStorage {
val jdbcDriver = "oracle.jdbc.driver.OracleDriver"
val user = "USER"
val password = "PASS"
val url = "jdbc:oracle:thin:@localhost:1521:oracle"
val dialect = oracleDialect
}
}
Mysql
import net.fwbrasil.activate.ActivateContext
import net.fwbrasil.activate.storage.relational.JdbcRelationalStorage
import net.fwbrasil.activate.storage.relational.mySqlDialect
object mysqlContext extends ActivateContext {
def contextName = "mysqlContext"
val storage = new SimpleJdbcRelationalStorage {
val jdbcDriver = "com.mysql.jdbc.Driver"
val user = "root"
val password = "root"
val url = "jdbc:mysql://127.0.0.1/test"
val dialect = mySqlDialect
}
}
MongoDB
import net.fwbrasil.activate.ActivateContext
import net.fwbrasil.activate.storage.mongo.MongoStorage
object mongoContext extends ActivateContext {
def contextName = "mongoContext"
val storage = new MongoStorage {
val host = "localhost"
override val port = 27017
val db = "dbName"
override val authentication = Option("user", "pass")
}
}
É importante que o nome do contexto seja único, porém você pode possuir vários contextos na mesma VM.
Para utilizar o contexto, importe ele:
import prevaylerContext._
Desta forma, as classes necessárias como Entity e Query estarão no escopo. As entidades devem estender do trait "Entity":
abstract class Pessoa(var nome: String) extends Entity
class PessoaFisica(nome: String, var nomeMae: String) extends Pessoa(nome)
class PessoaJuridica(nome: String, var diretor: PessoaFisica) extends Pessoa(nome)
É possível declarar as propriedades como val ou var, caso estas sejam imutáveis ou não.
Utilize as entidades sempre dentro de transações:
transactional {
val pessoa = new PessoaFisica("Fulano", "Maria")
pessoa.nome = "Fulano2"
println(pessoa.nome)
}
Não é necessário chamar um método como "store" ou "save" para adicionar a entidade. Apenas a crie, utilize, e ela será persistida.
Consultas:
Execute as consultas dentro de transações:
transactional {
val result =
query {
(person: Person) => where(person.name :== "Test") select(person)
}
for (person <- result)
println(person.name)
}
Os operadores de consulta disponíveis são :==, :<, :>, :<=, :>=, isNull, isNotNull, :||, :&&, like and regexp. Observe que as queries podem ser feitas sobre super classes (incluindo trait e abstract class).
Existem formas alternativas de consulta. Com o allWhere possível utilizar uma lista de critérios.
transactional {
val pessoaList1 = all[Pessoa]
val pessoaList2 = allWhere[PessoaFisica](_.nome :== "Teste", _.nomeMae :== "Mae")
}
Queries utilizando mais de uma entidade ou com propriedades aninhadas:
val q2 = query {
(empresa: PessoaJuridica, diretor: PessoaFisica) => where(empresa.diretor :== diretor) select (empresa, diretor)
}
val q3 = query {
(empresa: PessoaJuridica) => where(empresa.diretor.nome :== "Silva") select(empresa)
}
Obs.: Queries que envolvem mais de uma entidade não são suportadas pelo MongoStorage.
Para apagar uma entidade:
transactional {
for(pessoa <- all[Pessoa])
pessoa.delete
}
Tipicamente os blocos transacionais são controlados pelo framework. Porém é possível controlar a transação como segue:
val transaction = new Transaction
transactional(transaction) {
new PessoaFisica("Teste", "Mae")
}
transaction.commit
Definindo a propagação da transação:
transactional {
val pessoa = new PessoaFisica("Teste", "Mae")
transactional(mandatory) {
pessoa.nome = "Teste2"
}
println(pessoa.nome)
}
Transações aninhadas são um tipo de propagação:
transactional {
val pessoa = new PessoaFisica("Teste", "Mae")
transactional(nested) {
pessoa.nome = "Teste2"
}
println(pessoa.nome)
}
As propagações disponíveis são baseadas nas do EJB:
- required
- requiresNew
- mandatory
- notSupported
- supports
- never
- nested
Este é o mapeamento entre os tipos dos atributos das entidades e os tipos dos bancos de dados:
Tipo | Mysql | Oracle |
---|---|---|
ID | VARCHAR(50) | VARCHAR2(50) |
Int | INTEGER | INTEGER |
Boolean | BOOLEAN | NUMBER(1) |
Char | CHAR | CHAR |
String | VARCHAR | VARCHAR2 |
Float | DOUBLE | FLOAT |
Double | DOUBLE | DOUBLE PRECISION |
BigDecimal | DECIMAL | NUMBER |
Date | LONG | TIMESTAMP |
Calendar | LONG | TIMESTAMP |
Array[Byte] | BLOB | BLOB |
Entity | VARCHAR(50) | VARCHAR2(50) |
Enumeration | VARCHAR(20) | VARCHAR2(20) |
- Sempre adicione uma coluna "ID" nas tabelas das entidades.
- O nome da tabela é o nome da classe da entidade.
- O tipo AbstractInstant (JodaTime) segue o mesmo mapemanento do tipo Date.
O código é licenciado como LGPL.