jpa jdbc jndi_没有J2EE容器的JNDI和JPA
jpa jdbc jndi
我們希望通過盡可能簡(jiǎn)單的設(shè)置來測(cè)試一些JPA代碼。 計(jì)劃僅使用Java和Maven,不使用應(yīng)用程序服務(wù)器或其他J2EE容器。我們的JPA配置需要兩件事才能成功運(yùn)行:
- 數(shù)據(jù)庫來存儲(chǔ)數(shù)據(jù),
- JNDI訪問數(shù)據(jù)庫。
這篇文章分為兩個(gè)部分。 第一部分顯示了如何在測(cè)試中使用獨(dú)立的JNDI和嵌入式內(nèi)存數(shù)據(jù)庫。 其余各章說明了該解決方案的工作方式。
所有使用的代碼都可以在Github上找到 。 如果您對(duì)解決方案感興趣,但不想閱讀說明,請(qǐng)從Github下載項(xiàng)目并僅閱讀第一章。
JPA測(cè)試
本章說明如何在測(cè)試中使用我們的代碼來啟用獨(dú)立的JNDI和嵌入式內(nèi)存數(shù)據(jù)庫。 本文的其余部分將說明該解決方案的工作方式和原因。
該解決方案具有三個(gè)“ API”類:
- JNDIUtil – JNDI初始化,清理和一些便捷方法,
- InMemoryDBUtil –數(shù)據(jù)庫和數(shù)據(jù)源的創(chuàng)建/刪除,
- AbstractTestCase –在第一次測(cè)試之前清理數(shù)據(jù)庫,在每次測(cè)試之前清理JNDI。
我們使用Liquibase維護(hù)數(shù)據(jù)庫結(jié)構(gòu)。 如果您不想使用Liquibase,則必須自定義InMemoryDBUtil類。 調(diào)整方法createDatabaseStructure以執(zhí)行所需的操作。
Liquibase將所有需要的數(shù)據(jù)庫更改的列表保存在名為changelog的文件中。 除非另行配置,否則每個(gè)更改僅運(yùn)行一次。 即使將更改日志文件多次應(yīng)用于同一數(shù)據(jù)庫。
用法
從AbstractTestCase擴(kuò)展的任何測(cè)試用例都將:
- 在第一次測(cè)試之前刪除數(shù)據(jù)庫,
- 在每次測(cè)試之前安裝獨(dú)立的JNDI或刪除其中存儲(chǔ)的所有數(shù)據(jù),
- 每次測(cè)試之前,請(qǐng)對(duì)數(shù)據(jù)庫運(yùn)行Liquibase changelog。
JPA測(cè)試用例必須擴(kuò)展AbstractTestCase并重寫getInitialChangeLog方法。 該方法應(yīng)返回changelog文件位置。
public class DemoJPATest extends AbstractTestCase {private static final String CHANGELOG_LOCATION = "src/test/java/org/meri/jpa/simplest/db.changelog.xml";private static EntityManagerFactory factory;public DemoJPATest() {}@Overrideprotected String getInitialChangeLog() {return CHANGELOG_LOCATION;}@Test@SuppressWarnings("unchecked")public void testJPA() {EntityManager em = factory.createEntityManager();Query query = em.createQuery("SELECT x FROM Person x");List<Person> allUsers = query.getResultList();em.close();assertFalse(allUsers.isEmpty());}@BeforeClasspublic static void createFactory() {factory = Persistence.createEntityManagerFactory("Simplest");}@AfterClasspublic static void closeFactory() {factory.close();}}注意:在每次測(cè)試之前刪除數(shù)據(jù)庫會(huì)更清潔。 但是,刪除并重新創(chuàng)建數(shù)據(jù)庫結(jié)構(gòu)是昂貴的操作。 這會(huì)使測(cè)試用例放慢太多。 僅在上課之前這樣做似乎是一種合理的折衷。
雖然數(shù)據(jù)庫僅刪除一次,但更改日志在每次測(cè)試之前運(yùn)行。 看起來似乎很浪費(fèi),但是此解決方案具有一些優(yōu)勢(shì)。 首先, getInitialChangeLog方法不必是靜態(tài)的,并且可以在每個(gè)測(cè)試中覆蓋。 其次,配置為“ runAlways”的更改將在每次測(cè)試之前運(yùn)行,因此可能包含一些廉價(jià)的清理或其他初始化操作。
日本國家發(fā)展研究院
本章說明什么是JNDI,如何使用它以及如何配置它。 如果您對(duì)理論不感興趣,請(qǐng)?zhí)料乱徽隆?在此創(chuàng)建獨(dú)立的JNDI。
基本用法
JNDI允許客戶端通過名稱存儲(chǔ)和查找數(shù)據(jù)和對(duì)象。 數(shù)據(jù)存儲(chǔ)通過Context接口的實(shí)現(xiàn)來訪問。
以下代碼顯示了如何在JNDI中存儲(chǔ)數(shù)據(jù):
Context ctx = new InitialContext(); ctx.bind("jndiName", "value"); ctx.close();第二段代碼顯示了如何在JNDI中查找內(nèi)容:
Context ctx = new InitialContext(); Object result = ctx.lookup("jndiName"); ctx.close();嘗試在沒有J2EE容器的情況下運(yùn)行以上代碼,您會(huì)得到一個(gè)錯(cuò)誤:
javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initialat javax.naming.spi.NamingManager.getInitialContext(Unknown Source)at javax.naming.InitialContext.getDefaultInitCtx(Unknown Source)at javax.naming.InitialContext.getURLOrDefaultInitCtx(Unknown Source)at javax.naming.InitialContext.bind(Unknown Source)at org.meri.jpa.JNDITestCase.test(JNDITestCase.java:16)at ...該代碼不起作用,因?yàn)镮nitialContext類不是真實(shí)的數(shù)據(jù)存儲(chǔ)。 InitialContext類只能找到Context接口的另一個(gè)實(shí)例,并將所有工作委托給它。 它既無法存儲(chǔ)數(shù)據(jù)也無法找到它們。
上下文工廠
真正的上下文,即完成所有工作并能夠存儲(chǔ)/查找數(shù)據(jù)的上下文,必須由上下文工廠創(chuàng)建。 本節(jié)說明如何創(chuàng)建上下文工廠以及如何配置InitialContext以使用它。
每個(gè)上下文工廠必須實(shí)現(xiàn)InitialContextFactory接口,并且必須具有無參數(shù)構(gòu)造函數(shù):
package org.meri.jpa.jndi;public class MyContextFactory implements InitialContextFactory {@Overridepublic Context getInitialContext(Hashtable environment) throws NamingException {return new MyContext();}}我們的工廠返回一個(gè)簡(jiǎn)單的上下文,稱為MyContext 。 其lookup方法始終返回字符串“存儲(chǔ)的值”:
class MyContext implements Context {@Overridepublic Object lookup(Name name) throws NamingException {return "stored value";}@Overridepublic Object lookup(String name) throws NamingException {return "stored value";}.. the rest ... }JNDI配置在哈希表中的類之間傳遞。 鍵始終包含屬性名稱,而值包含屬性值。 由于初始上下文構(gòu)造函數(shù)InitialContext()沒有參數(shù),因此假定為空哈希表。 該類還具有一個(gè)替代構(gòu)造函數(shù),該構(gòu)造函數(shù)將配置屬性哈希表作為參數(shù)。
使用屬性"java.naming.factory.initial"來指定上下文工廠類名稱。 該屬性在Context.INITIAL_CONTEXT_FACTORY常量中定義。
Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "className");Context ctx = new InitialContext(environnement);下一步測(cè)試配置MyContextFactory并檢查創(chuàng)建的初始上下文是否返回“存儲(chǔ)值”,無論如何:
@Test @SuppressWarnings({ "unchecked", "rawtypes" }) public void testDummyContext() throws NamingException {Hashtable environnement = new Hashtable();environnement.put(Context.INITIAL_CONTEXT_FACTORY, "org.meri.jpa.jndi.MyContextFactory");Context ctx = new InitialContext(environnement);Object value = ctx.lookup("jndiName");ctx.close();assertEquals("stored value", value); }當(dāng)然,僅當(dāng)您可以將具有自定義屬性的哈希表提供給初始上下文構(gòu)造函數(shù)時(shí),此方法才有效。 這通常是不可能的。 大多數(shù)庫使用開頭所示的無參數(shù)構(gòu)造函數(shù)。 他們假設(shè)初始上下文類具有可用的默認(rèn)上下文工廠,并且無參數(shù)構(gòu)造函數(shù)將使用該工廠。
命名經(jīng)理
初始上下文使用NamingManager創(chuàng)建真實(shí)上下文。 命名管理器具有靜態(tài)方法getInitialContext(Hashtable env) ,該方法返回上下文的實(shí)例。 參數(shù)env包含用于構(gòu)建上下文的配置屬性。
默認(rèn)情況下,命名管理器讀取Context.INITIAL_CONTEXT_FACTORY從env哈希表,并創(chuàng)建指定的初始上下文工廠的實(shí)例。 然后,工廠方法將創(chuàng)建一個(gè)新的上下文實(shí)例。 如果未設(shè)置該屬性,則命名管理器將引發(fā)異常。
可以自定義命名管理員的行為。 NamingManager類具有方法setInitialContextFactoryBuilder 。 如果設(shè)置了初始上下文工廠構(gòu)建器,則命名管理器將使用它來創(chuàng)建上下文工廠。
您只能使用此方法一次。 已安裝的上下文工廠生成器無法更改。
try {MyContextFactoryBuilder builder = new MyContextFactoryBuilder();NamingManager.setInitialContextFactoryBuilder(builder); } catch (NamingException e) {// handle exception }初始上下文工廠構(gòu)建器必須實(shí)現(xiàn)InitialContextFactoryBuilder接口。 界面很簡(jiǎn)單。 它只有一個(gè)方法InitialContextFactory createInitialContextFactory(Hashtable env) 。
摘要
簡(jiǎn)而言之,初始上下文將實(shí)際的上下文初始化委托給命名管理器,命名管理器將其委托給上下文工廠。 上下文工廠由初始上下文工廠構(gòu)建器的實(shí)例創(chuàng)建。
獨(dú)立JNDI
我們將創(chuàng)建并安裝獨(dú)立的JNDI實(shí)現(xiàn)。 我們的獨(dú)立JNDI實(shí)現(xiàn)的入口點(diǎn)是JNDIUtil類。
在沒有應(yīng)用程序服務(wù)器的情況下啟用JNDI需要三件事:
- Context和InitialContextFactory接口的實(shí)現(xiàn),
- InitialContextFactoryBuilder接口的實(shí)現(xiàn),
- 初始上下文工廠構(gòu)建器的安裝以及清除所有存儲(chǔ)數(shù)據(jù)的能力。
上下文和工廠
我們從osjava項(xiàng)目中獲取了SimpleJNDI實(shí)現(xiàn),并對(duì)其進(jìn)行了修改以更好地滿足我們的需求。 該項(xiàng)目使用了新的BSD許可證 。
將SimpleJNDI maven依賴項(xiàng)添加到pom.xml中:
simple-jndisimple-jndi0.11.4.1SimpleJNDI帶有一個(gè)MemoryContext上下文,該上下文專門存在于內(nèi)存中。 它幾乎不需要任何配置,并且其狀態(tài)永遠(yuǎn)不會(huì)保存下來。 它幾乎滿足了我們的需求,除了兩件事:
- 它的close()方法刪除所有存儲(chǔ)的數(shù)據(jù),
- 每個(gè)實(shí)例默認(rèn)使用其自己的存儲(chǔ)。
大多數(shù)庫都假定close方法優(yōu)化了資源。 他們傾向于在每次加載或存儲(chǔ)數(shù)據(jù)時(shí)調(diào)用它。 如果close方法在存儲(chǔ)完所有數(shù)據(jù)后立即刪除它們,則上下文將無用。 我們必須擴(kuò)展MemoryContext類并重寫close方法:
@SuppressWarnings({"rawtypes"}) public class CloseSafeMemoryContext extends MemoryContext {public CloseSafeMemoryContext(Hashtable env) {super(env);}@Overridepublic void close() throws NamingException {// Original context lost all data on close();// That made it unusable for my tests. }}按照約定,建造者/工廠系統(tǒng)會(huì)為每次使用創(chuàng)建新的上下文實(shí)例。 如果它們不共享數(shù)據(jù),則不能使用JNDI在不同庫之間傳輸數(shù)據(jù)。
幸運(yùn)的是,這個(gè)問題也很容易解決。 如果環(huán)境哈希表包含值為"true"屬性"org.osjava.sj.jndi.shared" "true" ,則創(chuàng)建的內(nèi)存上下文將使用公共靜態(tài)存儲(chǔ)。 因此,我們的初始上下文工廠將創(chuàng)建CloseSafeMemoryContext實(shí)例,并將其配置為使用公共存儲(chǔ):
public class CloseSafeMemoryContextFactory implements InitialContextFactory {private static final String SHARE_DATA_PROPERTY = "org.osjava.sj.jndi.shared";public Context getInitialContext(Hashtable environment) throws NamingException {// clone the environnementHashtable sharingEnv = (Hashtable) environment.clone();// all instances will share stored dataif (!sharingEnv.containsKey(SHARE_DATA_PROPERTY)) {sharingEnv.put(SHARE_DATA_PROPERTY, "true");}return new CloseSafeMemoryContext(sharingEnv);;}}初始上下文工廠生成器
我們的構(gòu)建器的行為幾乎與原始命名管理器實(shí)現(xiàn)相同。 如果傳入環(huán)境中存在屬性Context.INITIAL_CONTEXT_FACTORY ,則將創(chuàng)建指定的工廠。
但是,如果缺少此屬性,則構(gòu)建器將創(chuàng)建CloseSafeMemoryContextFactory的實(shí)例。 原始的命名管理器將引發(fā)異常。
我們對(duì)InitialContextFactoryBuilder接口的實(shí)現(xiàn):
public InitialContextFactory createInitialContextFactory(Hashtable env) throws NamingException {String requestedFactory = null;if (env!=null) {requestedFactory = (String) env.get(Context.INITIAL_CONTEXT_FACTORY);}if (requestedFactory != null) {return simulateBuilderlessNamingManager(requestedFactory);}return new CloseSafeMemoryContextFactory(); }方法simulateBuilderlessNamingManager使用類加載器加載請(qǐng)求的上下文工廠:
private InitialContextFactory simulateBuilderlessNamingManager(String requestedFactory) throws NoInitialContextException {try {ClassLoader cl = getContextClassLoader();Class requestedClass = Class.forName(className, true, cl);return (InitialContextFactory) requestedClass.newInstance();} catch (Exception e) {NoInitialContextException ne = new NoInitialContextException(...);ne.setRootCause(e);throw ne;} }private ClassLoader getContextClassLoader() {return (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {public Object run() {return Thread.currentThread().getContextClassLoader();}}); }構(gòu)建器安裝和上下文清理
最后,我們必須安裝上下文工廠生成器。 當(dāng)我們想在測(cè)試中使用獨(dú)立的JNDI時(shí),我們還需要一種方法來清除測(cè)試之間的所有存儲(chǔ)數(shù)據(jù)。 兩者都在initializeJNDI方法內(nèi)部完成,該方法將在每次測(cè)試之前運(yùn)行:
public class JNDIUtil {public void initializeJNDI() {if (jndiInitialized()) {cleanAllInMemoryData();} else {installDefaultContextFactoryBuilder();}}}如果已經(jīng)設(shè)置了默認(rèn)上下文工廠構(gòu)建器,那么將初始化JNDI:
private boolean jndiInitialized() {return NamingManager.hasInitialContextFactoryBuilder();}安裝默認(rèn)上下文工廠生成器:
private void installDefaultContextFactoryBuilder() {try {NamingManager.setInitialContextFactoryBuilder(new ImMemoryDefaultContextFactoryBuilder());} catch (NamingException e) {//We can not solve the problem. We will let it go up without//having to declare the exception every time.throw new ConfigurationException(e);} }使用原始的方法實(shí)現(xiàn)close在MemoryContext類清理存儲(chǔ)數(shù)據(jù):
private void cleanAllInMemoryData() {CleanerContext cleaner = new CleanerContext();try {cleaner.close();} catch (NamingException e) {throw new RuntimeException("Memory context cleaning failed:", e);} }class CleanerContext extends MemoryContext {private static Hashtable environnement = new Hashtable();static {environnement.put("org.osjava.sj.jndi.shared", "true");}public CleanerContext() {super(environnement);}}
內(nèi)存數(shù)據(jù)庫
Apache Derby是用Java實(shí)現(xiàn)的開源關(guān)系數(shù)據(jù)庫。 根據(jù)Apache許可證2.0版提供。 Derby能夠在嵌入式模式下運(yùn)行。 嵌入式數(shù)據(jù)庫數(shù)據(jù)存儲(chǔ)在文件系統(tǒng)或內(nèi)存中。
對(duì)Derby的Maven依賴關(guān)系:
org.apache.derbyderby10.8.2.2創(chuàng)建數(shù)據(jù)源
使用EmbeddedDatasource類的實(shí)例連接到數(shù)據(jù)庫。 每當(dāng)數(shù)據(jù)庫名稱以“ memory:”開頭時(shí),數(shù)據(jù)源將使用一個(gè)內(nèi)存中實(shí)例。
以下代碼創(chuàng)建指向內(nèi)存數(shù)據(jù)庫實(shí)例的數(shù)據(jù)源。 如果數(shù)據(jù)庫尚不存在,將創(chuàng)建它:
private EmbeddedDataSource createDataSource() {EmbeddedDataSource dataSource = new EmbeddedDataSource();dataSource.setDataSourceName(dataSourceJndiName);dataSource.setDatabaseName("memory:" + databaseName);dataSource.setCreateDatabase("create");return dataSource; }刪除數(shù)據(jù)庫
清理數(shù)據(jù)庫的最簡(jiǎn)單方法是刪除并重新創(chuàng)建它。 創(chuàng)建嵌入式數(shù)據(jù)源的實(shí)例,將連接屬性“ drop”設(shè)置為“ true”,并調(diào)用其getConnection方法。 它將刪除數(shù)據(jù)庫并引發(fā)異常。
private static final String DATABASE_NOT_FOUND = "XJ004";private void dropDatabase() {EmbeddedDataSource dataSource = createDataSource();dataSource.setCreateDatabase(null);dataSource.setConnectionAttributes("drop=true");try {//drop the database; not the nicest solution, but worksdataSource.getConnection();} catch (SQLNonTransientConnectionException e) {//this is OK, database was dropped} catch (SQLException e) {if (DATABASE_NOT_FOUND.equals(e.getSQLState())) {//attempt to drop non-existend database//we will ignore this errorreturn ; }throw new ConfigurationException("Could not drop database.", e);}}
數(shù)據(jù)庫結(jié)構(gòu)
我們使用Liquibase創(chuàng)建數(shù)據(jù)庫結(jié)構(gòu)和測(cè)試數(shù)據(jù)。 數(shù)據(jù)庫結(jié)構(gòu)保存在所謂的變更日志文件中。 它是一個(gè)xml文件,但是如果您不想學(xué)習(xí)另一種xml語言,則可以包含DDL或SQL代碼。
Liquibase及其優(yōu)點(diǎn)不在本文討論范圍之內(nèi)。 此演示最相關(guān)的優(yōu)勢(shì)是它能夠?qū)ν粩?shù)據(jù)庫多次運(yùn)行同一變更日志。 每次運(yùn)行僅將新更改應(yīng)用于數(shù)據(jù)庫。 如果文件未更改,則什么都不會(huì)發(fā)生。
您可以將更改日志添加到j(luò)ar或war中,并在每次啟動(dòng)應(yīng)用程序時(shí)運(yùn)行它。 這樣可以確保數(shù)據(jù)庫始終更新為最新版本。 無需配置或安裝腳本。
將Liquibase依賴項(xiàng)添加到pom.xml:
org.liquibaseliquibase-core2.0.3以下更新日志創(chuàng)建一個(gè)名為Person的表,并將一個(gè)條目“斜杠– Simon Worth”放入其中:
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9 http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd"><changeSet id="1" author="meri"><comment>Create table structure for users and shared items.</comment><createTable tableName="person"><column name="user_id" type="integer"><constraints primaryKey="true" nullable="false" /></column><column name="username" type="varchar(1500)"><constraints unique="true" nullable="false" /></column><column name="firstname" type="varchar(1500)"/><column name="lastname" type="varchar(1500)"/><column name="homepage" type="varchar(1500)"/><column name="about" type="varchar(1500)"/></createTable></changeSet><changeSet id="2" author="meri" context="test"><comment>Add some test data.</comment><insert tableName="person"><column name="user_id" valueNumeric="1" /><column name="userName" value="slash" /><column name="firstName" value="Simon" /><column name="lastName" value="Worth" /><column name="homePage" value="http://www.slash.blogs.net" /><column name="about" value="I like nature and writing my blog. The blog contains my opinions about everything." /></insert></changeSet></databaseChangeLog>Liquibase的使用非常簡(jiǎn)單。 使用數(shù)據(jù)源創(chuàng)建新的Liquibase實(shí)例,運(yùn)行其update方法并處理所有聲明的異常:
private void initializeDatabase(String changelogPath, DataSource dataSource) {try {//create new liquibase instanceConnection sqlConnection = dataSource.getConnection();DatabaseConnection db = new DerbyConnection(sqlConnection);Liquibase liquibase = new Liquibase(changelogPath, new FileSystemResourceAccessor(), db);//update the databaseliquibase.update("test");} catch (SQLException e) {// We can not solve the problem. We will let it go up without// having to declare the exception every time.throw new ConfigurationException(DB_INITIALIZATION_ERROR, e);} catch (LiquibaseException e) {// We can not solve the problem. We will let it go up without// having to declare the exception every time.throw new ConfigurationException(DB_INITIALIZATION_ERROR, e);}}
結(jié)束
每次運(yùn)行測(cè)試時(shí),獨(dú)立的JNDI數(shù)據(jù)庫和嵌入式內(nèi)存數(shù)據(jù)庫都已啟動(dòng)并正在運(yùn)行。 盡管JNDI設(shè)置可能是通用的,但數(shù)據(jù)庫的構(gòu)建可能需要對(duì)項(xiàng)目進(jìn)行特定的修改。
可以從Github上免費(fèi)下載示例項(xiàng)目,并使用/修改任何有用的內(nèi)容。
參考:在This is Stuff博客上,我們的JCG合作伙伴 Maria Jurcovicova從JNDI和JPA Without J2EE Con??tainer運(yùn)行 。
翻譯自: https://www.javacodegeeks.com/2012/04/jndi-and-jpa-without-j2ee-container.html
jpa jdbc jndi
總結(jié)
以上是生活随笔為你收集整理的jpa jdbc jndi_没有J2EE容器的JNDI和JPA的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LTE-5G学习笔记27--5G-基础习
- 下一篇: 访问交流 | 成都经开区区长一行走访零数