مقدمة إلى Spring Spring مع Spring Data Mongo

مرحبا بالجميع. نسارع إلى تهنئة الطلاب على إجازتهم الاحترافية وإبلاغنا في شهر شباط (فبراير) بأننا سنبدأ الدورة التدريبية "Developer on the Spring Framework" ! هذا سيكون موضوع نشر اليوم.

رابطة العدل في خطر ، ولا يمكن إلا للفريد إنقاذ الجميع - مع نظام الإدارة الجديد مع Spring Boot و Spring Data و MongoDB.

بالنسبة لجامعة العدالة ، فقد جاءت الأوقات المظلمة عندما قرر Darkside الهائل استعباد البشرية. بدأ Batman و Wonder Woman في البحث عن أعضاء الرابطة ، وهناك نقطة مهمة واحدة مفقودة - نظام الإدارة المناسب لأعضاء رابطة العدالة.



ليس هناك ما يكفي من الوقت لإنشاء مشروع ضخم من نقطة الصفر ، لذلك ينقل باتمان هذه المهمة الصعبة إلى عزيزه ألفريد (روبن لا يمكن التنبؤ به للغاية) ، الذي يتذكر شيئًا يسمى Spring Boot ، والذي سيساعدك على عدم إضاعة الوقت في حل الفروق الدقيقة في إعداد المشروع والتبديل السريع لكتابة رمز التطبيق.
إذن يبدأ عزيزنا الفريد في استخدام Spring Boot لإنشاء نظام إدارة عضوية League League بسرعة. كحد أدنى ، أجزائه الخلفية ، حيث يعمل Batman مباشرة مع واجهة برمجة تطبيقات REST.

هناك العديد من الطرق المريحة لتكوين تطبيقات Spring Boot. في هذه المقالة ، سنركز على الطريقة التقليدية لتنزيل حزمة (Spring CLI) وإعدادها من نقطة الصفر على Ubuntu. يدعم Spring أيضًا تعبئة المشروع عبر الإنترنت باستخدام أداتهم . قم بتنزيل أحدث إصدار مستقر هنا . في هذه المقالة ، أنا أستخدم الإصدار 1.3.0.M1.

بعد تفريغ الأرشيف الذي تم تنزيله ، بالنسبة للمبتدئين ، قم بتكوين المعلمات التالية في ملف التعريف:

SPRING_BOOT_HOME=<extracted path>/spring-1.3.0.M1 PATH=$SPRING_BOOT_HOME/bin:$PATH 

ثم أضف ما يلي إلى ملف bashrc:

 <extracted-path>/spring-1.3.0.M1/shell-completion/bash/spring 

سيضيف ذلك الإكمال التلقائي إلى سطر الأوامر عند العمل مع spring-cli لإنشاء تطبيقات Spring Boot. تذكر أنه لتأكيد التغييرات التي تحتاجها "المصدر" لملف التعريف وملفات "bashrc".

تستخدم هذه المقالة مكدس التقنية التالية:

  • ربيع الراحة
  • بيانات الربيع
  • MongoDB.

نبدأ بإنشاء قالب مشروع تطبيق عن طريق تشغيل الأمر التالي. يرجى ملاحظة أنه يمكن تنزيل نموذج للمشروع من مستودع GitHub الخاص بي هنا .

 spring init -dweb,data-mongodb,flapdoodle-mongo --groupId com.justiceleague --artifactId justiceleaguemodule --build maven justiceleaguesystem 

سيؤدي هذا إلى إنشاء مشروع مخضرم مع Spring MVC و Spring Data مع نظام MongoDB المدمج.
افتراضيًا ، يقوم spring-cli بإنشاء مشروع يسمى "Demo". لذلك ، نحتاج إلى إعادة تسمية فئة التطبيق التي تم إنشاؤها ذات الصلة. إذا استخدمت المصادر من مستودع جيثب المذكور أعلاه ، فيمكنك تخطي هذه الخطوة.
باستخدام Spring Boot ، يعد تشغيل التطبيق بسيطًا مثل تشغيل ملف JAR الذي تم إنشاؤه بواسطة المشروع. الذي يدعو فقط فئة التطبيق المشروح بواسطةSpringBootApplication لتحميل Spring. دعونا نرى كيف يبدو:

 package com.justiceleague.justiceleaguemodule; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** *   spring boot,   -    *   * * @author dinuka * */ @SpringBootApplication public class JusticeLeagueManagementApplication { public static void main(String[] args) { SpringApplication.run(JusticeLeagueManagementApplication.class, args); } } 

ثم ننتقل إلى فئات المجال ، حيث يتم استخدام Spring Data جنبًا إلى جنب مع MongoDB لتحديد مستوى البيانات. فئة المجال هي كما يلي:

 package com.justiceleague.justiceleaguemodule.domain; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; /** *         ,  *    MongoDB * * @author dinuka * */ @Document(collection = "justiceLeagueMembers") public class JusticeLeagueMemberDetail { @Id private ObjectId id; @Indexed private String name; private String superPower; private String location; public JusticeLeagueMemberDetail(String name, String superPower, String location) { this.name = name; this.superPower = superPower; this.location = location; } public String getId() { return id.toString(); } public void setId(String id) { this.id = new ObjectId(id); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSuperPower() { return superPower; } public void setSuperPower(String superPower) { this.superPower = superPower; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } } 

تعتبر Spring Spring بديهية ، خاصة إذا كانت لديك خبرة في JPA / Hibernate. الشروح متشابهة جدا. الشيء الجديد الوحيد هو الشرح.

 @Document 

الذي يدل على اسم المجموعة في قاعدة بيانات Mongo. يوجد أيضًا فهرس محدد لأسماء الأبطال الخارقين ، حيث إن العديد من الاستعلامات ستتصل بالبحث حسب الاسم.

قدمت Spring Data وظيفة تعريف مستودع بسيط ، خارج الصندوق لدعم عمليات CRUD العادية وبعض عمليات القراءة. وبالتالي ، في تطبيقنا ، نستخدم إمكانيات مستودعات Spring Data ، وكذلك فئة المستودع كما يلي:

 package com.justiceleague.justiceleaguemodule.dao; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import com.justiceleague.justiceleaguemodule.domain.JusticeLeagueMemberDetail; public interface JusticeLeagueRepository extends MongoRepository < JusticeLeagueMemberDetail, String > { /** *        ,   . *  . * * @param superHeroName *        . * @return   {@link JusticeLeagueMemberDetail}   *  . */ @Query("{ 'name' : {$regex: ?0, $options: 'i' }}") JusticeLeagueMemberDetail findBySuperHeroName(final String superHeroName); } 

يتم تضمين عمليات الحفظ القياسية في وقت تشغيل Spring من خلال وكيل ، لذلك تحتاج فقط إلى تحديد فئة المجال في المستودع.

كما ترون ، يتم تحديد طريقة واحدة فقط. من خلال التعليق التوضيحي @Query نبحث عن بطل خارق يستخدم تعبيرات عادية. يعني الخيار "i" تجاهل الحالة عند محاولة العثور على تطابق في MongoDB.

ثم ننتقل إلى تنفيذ منطق تخزين الأعضاء الجدد في رابطة العدالة من خلال مستوى الخدمة.



 package com.justiceleague.justiceleaguemodule.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.justiceleague.justiceleaguemodule.constants.MessageConstants.ErrorMessages; import com.justiceleague.justiceleaguemodule.dao.JusticeLeagueRepository; import com.justiceleague.justiceleaguemodule.domain.JusticeLeagueMemberDetail; import com.justiceleague.justiceleaguemodule.exception.JusticeLeagueManagementException; import com.justiceleague.justiceleaguemodule.service.JusticeLeagueMemberService; import com.justiceleague.justiceleaguemodule.web.dto.JusticeLeagueMemberDTO; import com.justiceleague.justiceleaguemodule.web.transformer.DTOToDomainTransformer; /** *     {@link JusticeLeagueMemberService}   * ,     * * @author dinuka * */ @Service public class JusticeLeagueMemberServiceImpl implements JusticeLeagueMemberService { @Autowired private JusticeLeagueRepository justiceLeagueRepo; /** * {@inheritDoc} */ public void addMember(JusticeLeagueMemberDTO justiceLeagueMember) { JusticeLeagueMemberDetail dbMember = justiceLeagueRepo.findBySuperHeroName(justiceLeagueMember.getName()); if (dbMember != null) { throw new JusticeLeagueManagementException(ErrorMessages.MEMBER_ALREDY_EXISTS); } JusticeLeagueMemberDetail memberToPersist = DTOToDomainTransformer.transform(justiceLeagueMember); justiceLeagueRepo.insert(memberToPersist); } } 

مرة أخرى ، الأمر بسيط جدًا - إذا كان المشارك موجودًا بالفعل ، فسيحدث خطأ. خلاف ذلك ، أضف المشارك. لاحظ أن هذا يستخدم طريقة Spring Data المطبقة بالفعل في مستودع الإدراج ، والتي حددناها مسبقًا.

أخيرًا ، ألفريد مستعد لعرض الوظيفة الجديدة التي طورها من خلال واجهة برمجة تطبيقات REST باستخدام Spring REST حتى يبدأ Batman في إرسال التفاصيل عبر HTTP - إنه مستمر في التنقل:

 package com.justiceleague.justiceleaguemodule.web.rest.controller; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import com.justiceleague.justiceleaguemodule.constants.MessageConstants; import com.justiceleague.justiceleaguemodule.service.JusticeLeagueMemberService; import com.justiceleague.justiceleaguemodule.web.dto.JusticeLeagueMemberDTO; import com.justiceleague.justiceleaguemodule.web.dto.ResponseDTO; /** *    REST API . * * @author dinuka * */ @RestController @RequestMapping("/justiceleague") public class JusticeLeagueManagementController { @Autowired private JusticeLeagueMemberService memberService; /** *             * * @param justiceLeagueMember *    . * @return an instance of {@link ResponseDTO}     *   . */ @ResponseBody @ResponseStatus(value = HttpStatus.CREATED) @RequestMapping(method = RequestMethod.POST, path = "/addMember", produces = { MediaType.APPLICATION_JSON_VALUE }, consumes = { MediaType.APPLICATION_JSON_VALUE }) public ResponseDTO addJusticeLeagueMember(@Valid @RequestBody JusticeLeagueMemberDTO justiceLeagueMember) { ResponseDTO responseDTO = new ResponseDTO(ResponseDTO.Status.SUCCESS, MessageConstants.MEMBER_ADDED_SUCCESSFULLY); try { memberService.addMember(justiceLeagueMember); } catch (Exception e) { responseDTO.setStatus(ResponseDTO.Status.FAIL); responseDTO.setMessage(e.getMessage()); } return responseDTO; } } 

Batman ليس كافيًا ، لذلك نحن نقدم وظائف في شكل حمولة JSON ، على الرغم من أن Alfred سيكون من الطراز القديم ويفضل XML.

صديقنا القديم ألفريد هو أحد أتباع TDD (تطوير يحركه الاختبار ، وترجم "التطوير من خلال الاختبار") ، لذلك فهو يريد اختبار الوظيفة. ولذا فإننا ننظر إلى اختبارات التكامل التي كتبها ألفريد للتأكد من أن الإصدار الأولي لنظام إدارة جامعة العدل صحيح ويمكن التنبؤ به. يرجى ملاحظة أننا هنا نعرض اختبارات REST API فقط. احتضنت Alfred حجمًا أكبر ، يمكن العثور عليه في مستودع GitHub .

 package com.justiceleague.justiceleaguemodule.test.util; import java.io.IOException; import java.net.UnknownHostException; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import com.fasterxml.jackson.databind.ObjectMapper; import com.justiceleague.justiceleaguemodule.domain.JusticeLeagueMemberDetail; import de.flapdoodle.embed.mongo.MongodExecutable; import de.flapdoodle.embed.mongo.MongodStarter; import de.flapdoodle.embed.mongo.config.IMongodConfig; import de.flapdoodle.embed.mongo.config.MongodConfigBuilder; import de.flapdoodle.embed.mongo.config.Net; import de.flapdoodle.embed.mongo.distribution.Version; /** *     ,     , *            . * * @author dinuka * */ @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public abstract class BaseIntegrationTest { @Autowired protected MockMvc mockMvc; protected ObjectMapper mapper; private static MongodExecutable mongodExecutable; @Autowired protected MongoTemplate mongoTemplate; @Before public void setUp() { mapper = new ObjectMapper(); } @After public void after() { mongoTemplate.dropCollection(JusticeLeagueMemberDetail.class); } /** *      mongodb     *  . * * @throws UnknownHostException * @throws IOException */ @BeforeClass public static void beforeClass() throws UnknownHostException, IOException { MongodStarter starter = MongodStarter.getDefaultInstance(); IMongodConfig mongoConfig = new MongodConfigBuilder().version(Version.Main.PRODUCTION) .net(new Net(27017, false)).build(); mongodExecutable = starter.prepare(mongoConfig); try { mongodExecutable.start(); } catch (Exception e) { closeMongoExecutable(); } } @AfterClass public static void afterClass() { closeMongoExecutable(); } private static void closeMongoExecutable() { if (mongodExecutable != null) { mongodExecutable.stop(); } } } 

 package com.justiceleague.justiceleaguemodule.web.rest.controller; import org.hamcrest.beans.SamePropertyValuesAs; import org.junit.Assert; import org.junit.Test; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import com.justiceleague.justiceleaguemodule.constants.MessageConstants; import com.justiceleague.justiceleaguemodule.constants.MessageConstants.ErrorMessages; import com.justiceleague.justiceleaguemodule.domain.JusticeLeagueMemberDetail; import com.justiceleague.justiceleaguemodule.test.util.BaseIntegrationTest; import com.justiceleague.justiceleaguemodule.web.dto.JusticeLeagueMemberDTO; import com.justiceleague.justiceleaguemodule.web.dto.ResponseDTO; import com.justiceleague.justiceleaguemodule.web.dto.ResponseDTO.Status; /** *      REST-,  * {@link JusticeLeagueManagementController} * * @author dinuka * */ public class JusticeLeagueManagementControllerTest extends BaseIntegrationTest { /** *          *   . * * @throws Exception */ @Test public void testAddJusticeLeagueMember() throws Exception { JusticeLeagueMemberDTO flash = new JusticeLeagueMemberDTO("Barry Allen", "super speed", "Central City"); String jsonContent = mapper.writeValueAsString(flash); String response = mockMvc .perform(MockMvcRequestBuilders.post("/justiceleague/addMember") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON).content(jsonContent)) .andExpect(MockMvcResultMatchers.status().isCreated()).andReturn().getResponse().getContentAsString(); ResponseDTO expected = new ResponseDTO(Status.SUCCESS, MessageConstants.MEMBER_ADDED_SUCCESSFULLY); ResponseDTO receivedResponse = mapper.readValue(response, ResponseDTO.class); Assert.assertThat(receivedResponse, SamePropertyValuesAs.samePropertyValuesAs(expected)); } /** *   ,       *    ,     . * * @throws Exception */ @Test public void testAddJusticeLeagueMemberWhenMemberAlreadyExists() throws Exception { JusticeLeagueMemberDetail flashDetail = new JusticeLeagueMemberDetail("Barry Allen", "super speed","Central City"); mongoTemplate.save(flashDetail); JusticeLeagueMemberDTO flash = new JusticeLeagueMemberDTO("Barry Allen", "super speed", "Central City"); String jsonContent = mapper.writeValueAsString(flash); String response = mockMvc .perform(MockMvcRequestBuilders.post("/justiceleague/addMember"). accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON).content(jsonContent)) .andExpect(MockMvcResultMatchers.status().isCreated()).andReturn().getResponse() .getContentAsString(); ResponseDTO expected = new ResponseDTO(Status.FAIL, ErrorMessages.MEMBER_ALREDY_EXISTS); ResponseDTO receivedResponse = mapper.readValue(response, ResponseDTO.class); Assert.assertThat(receivedResponse, SamePropertyValuesAs.samePropertyValuesAs(expected)); } /** *   ,      , *          JSON . *    -  . * * @throws Exception */ @Test public void testAddJusticeLeagueMemberWhenNameNotPassedIn() throws Exception { //     ,   //     . JusticeLeagueMemberDTO flash = new JusticeLeagueMemberDTO (null, "super speed", "Central City"); String jsonContent = mapper.writeValueAsString(flash); mockMvc.perform(MockMvcRequestBuilders.post("/justiceleague/addMember") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON).content(jsonContent)) .andExpect(MockMvcResultMatchers.status().is4xxClientError()); } } 

ربما هذا كل شيء. بمساعدة Spring Boot ، كان Alfred قادرًا على إنشاء نظام إدارة League League يعمل بسرعة منخفضة مع واجهة برمجة تطبيقات REST. بمرور الوقت ، سنقوم بتوسيع وظائف هذا التطبيق ونرى كيف يجد ألفريد طريقة لنشر التطبيق من خلال Docker إلى مثيل Amazon AWS الذي تديره Kubernetes. أوقات مثيرة تنتظرنا ، لذلك تواصل!

تقليديًا ، ننتظر تعليقاتك وندعوك إلى ندوة عبر الإنترنت مفتوحة ، والتي ستعقد في 6 فبراير بواسطة مرشح العلوم الفيزيائية والرياضية - يوري دفورشتسكي .

Source: https://habr.com/ru/post/ar437410/


All Articles