Статьи

Java EE + MongoDb с Apache TomEE и Jongo Starter Project

Знаете MongoDB и Java EE , но не знаете точно, как интегрировать их оба? Вы много читаете по этой теме, но не нашли решения, подходящего для этой цели? Этот стартовый проект для вас:

Вы узнаете, как использовать MongoDB и Java EE модным способом, не полагаясь на среду Spring Data MongoDB, но с «похожими» базовыми функциями.

Единственное, что лучше, чем архетип Maven, — это репозиторий, который вы можете раскошелиться со всем уже настроенным. Пропустить документацию и просто раскошелиться. Этот стартовый проект содержит:

Пример довольно прост, мы хотим хранить цвета внутри коллекции MongoDB .

Наш POJO похож на:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Color {
 
    @ObjectId
    private String _id;
     
    private String name;
    private int r;
    private int g;
    private int b;
     
    public Color() {
        super();
    }
 
    public Color(String name, int r, int g, int b) {
        super();
        this.name = name;
        this.r = r;
        this.g = g;
        this.b = b;
    }
 
        // getters and setters
}

Обратите внимание, что мы используем аннотацию @ObjectId, предоставленную Jongo, чтобы установить это поле как MongoDB id. Более того, поскольку он называется _id, идентификатор будет установлен автоматически.

Тогда уровень обслуживания:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Singleton
@Lock(LockType.READ)
public abstract class ColorService implements InvocationHandler {
 
    @JongoCollection("color")
    @Inject
    MongoCollection colorMongoCollection;
     
    @Insert
    public abstract Color createColor(Color c);
     
    @Remove
    public abstract int removeAllColors();
     
    @FindById
    public abstract Color findColorById(String id);
     
    @FindOne("{name:#}")
    public abstract Color findColorByColorName(String colorName);
     
    @Find("{r:#}")
    public abstract Iterable<Color> findColorByRed(int r);
     
    public long countColors() {
        return colorMongoCollection.count();
    }
     
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        return PersistenceHandler.invoke(colorMongoCollection, method, args);
    }
     
}

Обратите внимание, что кода не так много, но некоторые моменты действительно интересны. Давайте проанализируем их.

@Singleton используется для определения EJB как синглтона, он также работает с @Stateless , для пользователей Java EE здесь нет новостей.

Класс абстрактный. Почему? Потому что это позволяет нам не реализовывать все методы, а определять их.

Также реализует java.lang.reflect.InvocationHandler . Это потому, что мы хотим использовать одну действительно интересную функцию, которая позволяет нам создать запасной метод, называемый invoke . Для любого метода, определенного, но не реализованного, этот метод вызывается.

У нас есть класс MongoCollection (из проекта Jongo ), который он внедряет. MongoCollection представляет коллекцию в MongoDB . Поскольку нам нужно установить, с какой коллекцией мы хотим работать, создается аннотация @JongoCollection, чтобы вы могли передать имя коллекции бэкэнда. Обратите внимание, что MongoCollection создается контейнером CDI с использованием нашего собственного производителя. Опять нет новостей для пользователей CDI .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@ApplicationScoped
public class MongoCollectionProducer {
 
    @Inject
    DB mongoDb;
     
    Jongo jongo;
 
    @PostConstruct
    public void initialize() throws UnknownHostException {
        jongo = new Jongo(mongoDb);
    }
 
 
    @Produces
    @JongoCollection
    MongoCollection collection(InjectionPoint injectionPoint) {
 
        JongoCollection jongoCollectionAnnotation = Reflection.annotation(injectionPoint
                .getQualifiers(), JongoCollection.class);
 
        if(jongoCollectionAnnotation != null) {
            String collectionName = jongoCollectionAnnotation.value();
            return jongo.getCollection(collectionName);
        }
 
        throw new IllegalArgumentException();
    }
 
 
}

Тогда есть много методов, которые представляют CRUD- операции. Обратите внимание, что они не реализованы, они только аннотируются с помощью @Insert , @Find , @Remove ,…, чтобы установить, какова цель метода, который мы хотим выполнить. Некоторые из них, такие как средства поиска или удаления, могут получать Jongo- подобный запрос для выполнения. А также метод с именем countColors, который, как вы можете видеть, можно реализовать как пользовательский метод, не полагаясь на логику, реализованную в методе invoke .

И, наконец, метод invoke . Этот метод будет вызываться для всех абстрактных методов и просто отправляется в класс PersistenceHandler , который фактически является классом util для Jongo для выполнения требуемой операции.

И все, довольно просто, и если вы хотите добавить новые абстрактные операции, вам нужно только реализовать их внутри класса PersistenceHandler .

Некоторые из вас могут задаться вопросом, почему я использую аннотации, а не типичный подход Spring Data, где имя метода указывает на операцию. Вы также можете реализовать этот подход, это простой вопрос создания регулярного выражения внутри класса PersistenceHandler вместо if / else с аннотациями, но я предпочитаю подход с аннотациями. Быстрее во времени выполнения, очистите, и, например, вы можете изменить название аннотации с @Find на @Buscar (испанский эквивалент), не беспокоясь о том, что вы нарушаете какое-то регулярное выражение.

И наконец тест:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
@RunWith(Arquillian.class)
public class ColorTest {
 
    private static final String MONGODB_RESOURCE = "<resources>\n" +
            "    <Resource id=\"mongoUri\" class-name=\"com.mongodb.MongoClientURI\" constructor=\"uri\">\n" +
            "        uri  mongodb://localhost/test\n" +
            "    </Resource>\n" +
            "</resources>";
     
    @Deployment
    public static JavaArchive createDeployment() {
        JavaArchive javaArchive = ShrinkWrap.create(JavaArchive.class)
                .addPackages(true, Color.class.getPackage())
                .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
                .addAsManifestResource(new StringAsset(MONGODB_RESOURCE), "resources.xml")
                .merge(getJongoAndMongoDependecies());
         
        return javaArchive;
    }
 
    private static JavaArchive getJongoAndMongoDependecies() {
        JavaArchive[] javaArchives = Maven.configureResolver()
                .loadPomFromFile("pom.xml")
                .resolve("org.mongodb:mongo-java-driver", "org.jongo:jongo")
                .withTransitivity()
                .as(JavaArchive.class);
 
        JavaArchive mergedLibraries = ShrinkWrap.create(JavaArchive.class);
 
        for (JavaArchive javaArchive : javaArchives) {
            mergedLibraries.merge(javaArchive);
        }
 
        return mergedLibraries;
    }
 
    @EJB
    ColorService colorService;
 
    @Before
    public void cleanDatabase() {
       colorService.removeAllColors();
    }
 
    @Test
    public void should_insert_color() {
 
        Color color = colorService.createColor(new Color("red", 255, 0, 0));
 
        assertThat(color.getId(), notNullValue());
        assertThat(color.getName(), is("red"));
        assertThat(color.getR(), is(255));
        assertThat(color.getB(), is(0));
        assertThat(color.getG(), is(0));
 
    }
 
 
    @Test
    public void should_count_number_of_colors() {
 
        colorService.createColor(new Color("red", 255, 0, 0));
        colorService.createColor(new Color("blue", 0, 0, 255));
 
        assertThat(colorService.countColors(), is(2L));
 
    }
 
    @Test
    public void should_find_colors_by_id() {
 
        Color originalColor = colorService.createColor(new Color("red", 255, 0, 0));
 
        Color color = colorService.findColorById(originalColor.getId());
 
        assertThat(color.getId(), notNullValue());
        assertThat(color.getName(), is("red"));
        assertThat(color.getR(), is(255));
        assertThat(color.getB(), is(0));
        assertThat(color.getG(), is(0));
 
    }
 
    @Test
    public void should_find_colors_by_name() {
 
        colorService.createColor(new Color("red", 255, 0, 0));
 
        Color color = colorService.findColorByColorName("red");
 
        assertThat(color.getId(), notNullValue());
        assertThat(color.getName(), is("red"));
        assertThat(color.getR(), is(255));
        assertThat(color.getB(), is(0));
        assertThat(color.getG(), is(0));
 
 
    }
 
    @Test
    public void should_find_colors_by_red() {
 
        colorService.createColor(new Color("red", 255, 0, 0));
        colorService.createColor(new Color("white", 255, 255, 255));
 
        Iterable<Color> colorByRed = colorService.findColorByRed(255);
 
        assertThat(colorByRed, hasItems(new Color("red", 255, 0, 0), new Color("white", 255, 255, 255)));
 
    }
 
}

Это тест Arquillian, в котором нет ничего особенного, кроме одной строки:

.addAsManifestResource (новый StringAsset (MONGODB_RESOURCE), «resources.xml»)

Поскольку мы используем Apache TomEE, мы используем способ настройки элементов, которые будут использоваться в нашем коде как javax.annotation.Resource .

Содержимое META-INF / resources.xml будет:

1
2
3
4
5
<resources>
  <Resource id="mongoUri" class-name="com.mongodb.MongoClientURI" constructor="uri">
    uri  mongodb://localhost/test
  </Resource>
</resources>

и затем мы используем в нашем производителе MongoClient для создания экземпляра MongoClient для использования внутри кода. Обратите внимание, что мы используем @Resource в качестве любого стандартного ресурса, такого как DataSource , но на самом деле MongoClientURI внедряется:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@ApplicationScoped
public class MongoDBProducer {
 
    @Resource(name = "mongoUri")
    private MongoClientURI mongoClientURI;
     
    private DB db;
 
    @PostConstruct
    public void init() throws UnknownHostException {
        MongoClient mongoClient = new MongoClient(mongoClientURI);
        db =  mongoClient.getDB(mongoClientURI.getDatabase());
    }
 
    @Produces
    public DB createDB() {
        return db;
    }
 
}

поэтому на самом деле соединение Mongo настроено в файле META-INF / resources.xml, и благодаря TomEE мы можем ссылаться на него как на любой стандартный ресурс.

Если вы собираетесь использовать другой сервер приложений, вы можете изменить этот подход на предоставленный им, или, если хотите, можете использовать расширения DeltaSpike или свой собственный метод. Кроме того, поскольку база данных MongoClient получена из метода, помеченного @Produces, вы можете внедрить ее в свой код в любом месте, так что вы можете пропустить слой абстрактных служб, если хотите.

Каковы преимущества этого подхода?

Во-первых, это решение Java EE , вы можете использовать его независимо от среды Spring или любой другой библиотеки. Вы реализуете то, что вам нужно, вы не загружаете кучу библиотек просто для доступа к MongoDB с каким-либо отображением объектов.

Также, как вы можете видеть, код довольно прост, и в нем нет никакой магии, вы можете отладить его без проблем, или даже улучшить или изменить в зависимости от ваших потребностей. Код принадлежит вам и ожидает изменения. Вы хотите использовать нативные объекты MongoDB вместо Jongo ? Нет проблем, вы можете реализовать это. Более того, слоев не так много, фактически только один ( PersistenceHandler ), поэтому решение довольно быстрое с точки зрения исполнения.

Конечно, это не означает, что вы не можете использовать Spring Data MongoDB . Это действительно интересный фреймворк, поэтому, если вы уже используете Spring , продолжайте, но если вы планируете использовать полное решение J ava EE , то клонируйте этот проект и начните использовать MongoDB, не проводя каких-либо исследований в сети. о том, как интегрировать их обоих.

Ссылка: Java EE + MongoDb с Apache TomEE и Jongo Starter Project от нашего партнера JCG Алекса Сото в блоге One Jar to Rule All .