Get familiar with the Spring Data and the Spring Boot Data features.

Time: 60 minutes (20 minutes per section).

Code Snippet Manager JDBC

You will continue with the Code Snippet Manager code, but this time using a persistence engine. You will reuse the code from previous labs.

  1. Open a browser and hit the url: http://start.spring.io

  2. Click the Switch to the full version link.

  3. Fill out the Code Snippet Manager JDBC Project metadata with (See Figure 1.0):

    Table 1. Code Snippet Manager JDBC App - metadata
    Property Value

    Group:

    io.pivotal.workshop

    Artifact:

    code-snippet-manager-jdbc

    Name:

    code-snippet-manager-jdbc

    Package Name:

    io.pivotal.workshop.snippet

    Dependencies:

    Web, DevTools, Groovy Templates, JDBC, H2, MySQL

    Spring Boot:

    2.0.0.M7

    Figure 1.0: Spring Initializr - http://start.spring.io/

    SpringInitializr

    Tip
    You can choose either Maven or Gradle project types.
  4. Type Web, DevTools, Groovy Templates, JDBC, H2, and MySQL in the Dependencies field and press Enter.

  5. Click the Generate Project button.

  6. Unzip the file in any directory you want.

  7. Import your project in any IDE you want.

  8. Copy all the packages (with code) into the new project.

  9. The classes in io.pivotal.workshop.snippet.domain: Code, Language and Snippet should be the same.

  10. Create a new class CrossSnippetLanguageCode in the domain package:

    package io.pivotal.workshop.snippet.domain;
    
    public class CrossSnippetLanguageCode {
    
            private String snippetId;
            private String languageId;
            private String codeId;
    
            public CrossSnippetLanguageCode() {
            }
    
            public CrossSnippetLanguageCode(String snippetId, String languageId, String codeId) {
    
                    this.snippetId = snippetId;
                    this.languageId = languageId;
                    this.codeId = codeId;
            }
    
            public String getSnippetId() {
                    return snippetId;
            }
    
            public void setSnippetId(String snippetId) {
                    this.snippetId = snippetId;
            }
    
            public String getLanguageId() {
                    return languageId;
            }
    
            public void setLanguageId(String languageId) {
                    this.languageId = languageId;
            }
    
            public String getCodeId() {
                    return codeId;
            }
    
            public void setCodeId(String codeId) {
                    this.codeId = codeId;
            }
    
    }

    this class will hold the relationship between the Code and Language within the Snippet class.

  11. Because you will reuse the SimpleRepository interface, is necessary modify all the implementations. Modify the code accordingly:

    io.pivotal.workshop.snippet.repository.LanguageRepository.java
    package io.pivotal.workshop.snippet.repository;
    
    import java.sql.PreparedStatement;
    import java.util.Collection;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.dao.EmptyResultDataAccessException;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    import io.pivotal.workshop.snippet.domain.Language;
    import io.pivotal.workshop.snippet.repository.mapper.LanguageRowMapper;
    
    @Repository
    public class LanguageRepository implements SimpleRepository<Language>{
    
        private final String SQL_FIND_ALL = "select * from language";
        private final String SQL_FIND_ONE = "select * from language where name = ?";
        private final String SQL_INSERT = "insert into language(id,name,syntax) values(?,?,?)";
        private final String SQL_UPDATE = "update language set name = ?, syntax = ? where id = ?";
    
        private JdbcTemplate jdbcTemplate;
    
        @Autowired
        public LanguageRepository(JdbcTemplate jdbcTemplate){
                        this.jdbcTemplate = jdbcTemplate;
        }
    
        @Override
            public Iterable<Language> findAll(){
                    return this.jdbcTemplate.query(SQL_FIND_ALL, new LanguageRowMapper());
            }
    
        @Override
            public void saveAll(Collection<Language> languages){
                    languages.forEach( lang -> saveAll(lang) );
            }
    
        @Override
            public Language findById(String name) {
                        try {
                                return this.jdbcTemplate.queryForObject(SQL_FIND_ONE, new Object[]{name}, new LanguageRowMapper());
                    } catch (EmptyResultDataAccessException ex){
                                return null;
                        }
            }
    
        @Override
            public Language saveAll(Language item) {
                    assert item.getName() != null;
    
                    Language language = this.findById(item.getName());
    
                    if(language == null) {
                            this.jdbcTemplate.update( psc -> {
                                    PreparedStatement ps = psc.prepareStatement(SQL_INSERT);
                                    ps.setString(1, item.getId());
                                    ps.setString(2, item.getName());
                                    ps.setString(3, item.getSyntax());
                                    return ps;
                            });
                            return item;
                    }else {
                            this.jdbcTemplate.update( psc -> {
                                    PreparedStatement ps = psc.prepareStatement(SQL_UPDATE);
                                    ps.setString(1, item.getName());
                                    ps.setString(2, item.getSyntax());
                                    ps.setString(3, item.getId());
                                    return ps;
                            });
                            return language;
                    }
            }
    }
    io.pivotal.workshop.snippet.repository.CodeRepository.java
    package io.pivotal.workshop.snippet.repository;
    
    import java.sql.PreparedStatement;
    import java.util.Collection;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.dao.EmptyResultDataAccessException;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    import io.pivotal.workshop.snippet.domain.Code;
    import io.pivotal.workshop.snippet.repository.mapper.CodeRowMapper;
    
    @Repository
    public class CodeRepository implements SimpleRepository<Code> {
    
            private final String SQL_FIND_ALL = "select * from code";
            private final String SQL_FIND_ONE = "select * from code where id = ?";
            private final String SQL_INSERT = "insert into code(id,source) values(?,?)";
            private final String SQL_UPDATE = "update code set source = ? where id = ?";
    
            private JdbcTemplate jdbcTemplate;
    
            @Autowired
            public CodeRepository(JdbcTemplate jdbcTemplate) {
                    this.jdbcTemplate = jdbcTemplate;
            }
    
            @Override
            public List<Code> findAll() {
                    return this.jdbcTemplate.query(SQL_FIND_ALL, new CodeRowMapper());
            }
    
            @Override
            public void saveAll(Collection<Code> items) {
                    items.forEach(code -> this.saveAll(code));
            }
    
            @Override
            public Code findById(String id) {
                    try {
                            return this.jdbcTemplate.queryForObject(SQL_FIND_ONE, new Object[] { id }, new CodeRowMapper());
                    } catch (EmptyResultDataAccessException ex) {
                            return null;
                    }
            }
    
            @Override
            public Code saveAll(Code item) {
                    assert item.getSource() != null;
    
                    Code code = this.findById(item.getId());
    
                    if (code == null) {
                            this.jdbcTemplate.update(psc -> {
                                    PreparedStatement ps = psc.prepareStatement(SQL_INSERT);
                                    ps.setString(1, item.getId());
                                    ps.setString(2, item.getSource());
                                    return ps;
                            });
                            return item;
                    } else {
                            this.jdbcTemplate.update(psc -> {
                                    PreparedStatement ps = psc.prepareStatement(SQL_UPDATE);
                                    ps.setString(1, item.getSource());
                                    ps.setString(2, item.getId());
                                    return ps;
                            });
                            return code;
                    }
            }
    
    }
    io.pivotal.workshop.snippet.repository.SnippetRepository.java
    package io.pivotal.workshop.snippet.repository;
    
    import java.sql.PreparedStatement;
    import java.text.SimpleDateFormat;
    import java.util.Collection;
    import java.util.Date;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.dao.EmptyResultDataAccessException;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    import io.pivotal.workshop.snippet.domain.CrossSnippetLanguageCode;
    import io.pivotal.workshop.snippet.domain.Snippet;
    import io.pivotal.workshop.snippet.repository.mapper.SnippetRowMapper;
    
    @Repository
    public class SnippetRepository implements SimpleRepository<Snippet> {
    
            private final String SQL_FIND_ALL = "select s.*,l.id lang_id, l.name lang_name, l.syntax lang_syntax, c.id code_id, c.source code_source from cross_snippet_language_code cx " +
                            "inner join snippet s on cx.snippet_id = s.id " +
                            "inner join language l on cx.language_id = l.id " +
                            "inner join code c on cx.code_id = c.id ";
    
            private final String SQL_FIND_ONE = SQL_FIND_ALL + " where s.id = ?";
            private final String SQL_INSERT = "insert into snippet(id,title, keywords, description, created, modified) values(?,?,?,?,?,?)";
            private final String SQL_UPDATE = "update snippet set title = ?, keywords = ?, description = ?, modified = ? where id = ?";
    
            private JdbcTemplate jdbcTemplate;
            private LanguageRepository langRepo;
            private CodeRepository codeRepo;
            private CrossSnippetLanguageCodeRepository crossRepo;
    
            @Autowired
            public SnippetRepository(JdbcTemplate jdbcTemplate, LanguageRepository langRepo, CodeRepository codeRepo, CrossSnippetLanguageCodeRepository crossRepo) {
                    this.jdbcTemplate = jdbcTemplate;
                    this.langRepo = langRepo;
                    this.codeRepo = codeRepo;
                    this.crossRepo = crossRepo;
            }
    
            @Override
            public List<Snippet> findAll() {
                    return this.jdbcTemplate.query(SQL_FIND_ALL, new SnippetRowMapper());
            }
    
            @Override
            public void saveAll(Collection<Snippet> items) {
                    items.forEach(code -> this.saveAll(code));
            }
    
            @Override
            public Snippet findById(String id) {
                    try {
                            return this.jdbcTemplate.queryForObject(SQL_FIND_ONE, new Object[] { id }, new SnippetRowMapper());
                    } catch (EmptyResultDataAccessException ex) {
                            return null;
                    }
            }
    
            @Override
            public Snippet saveAll(final Snippet item) {
                    assert item.getId() != null;
                    assert item.getTitle() != null;
                    assert item.getLang() != null;
                    assert item.getCode() != null;
    
                    Snippet snippet = this.findById(item.getId());
    
                    Date date = new Date();
                    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    
                    if (snippet == null) {
    
                            this.crossRepo.saveAll(new CrossSnippetLanguageCode(item.getId(), item.getLang().getId(), item.getCode().getId()));
                            this.langRepo.saveAll(item.getLang());
                            this.codeRepo.saveAll(item.getCode());
    
                            this.jdbcTemplate.update(psc -> {
                                    PreparedStatement ps = psc.prepareStatement(SQL_INSERT);
                                    ps.setString(1, item.getId());
                                    ps.setString(2, item.getTitle());
                                    ps.setString(3, item.getKeywords());
                                    ps.setString(4, item.getDescription());
                                    ps.setString(5, dateFormat.format(date));
                                    ps.setString(6, dateFormat.format(date));
                                    return ps;
                            });
    
                            return item;
    
                    } else {
                            this.crossRepo.saveAll(new CrossSnippetLanguageCode(snippet.getId(), snippet.getLang().getId(), snippet.getCode().getId()));
                            this.langRepo.saveAll(item.getLang());
                            this.codeRepo.saveAll(item.getCode());
    
                            this.jdbcTemplate.update(psc -> {
                                    PreparedStatement ps = psc.prepareStatement(SQL_UPDATE);
                                    ps.setString(1, item.getTitle());
                                    ps.setString(2, item.getKeywords());
                                    ps.setString(3, item.getDescription());
                                    ps.setString(4, dateFormat.format(date));
                                    ps.setString(5, item.getId());
                                    return ps;
                            });
    
                            return snippet;
                    }
            }
    
    
    }

    Every single class is using the JdbcTemplate (with some methods like: queryForObject, query and update) and the RowMapper, analyze the code and review the SQL statements.

  12. As you already guess from the code above, the SnippetRepository class has on its constructor the CrossSnippetLanguageCodeRepository reference. Create this class:

    io.pivotal.workshop.snippet.repository.CrossSnippetLanguageCodeRepository.java
    package io.pivotal.workshop.snippet.repository;
    
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.util.Collection;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.dao.EmptyResultDataAccessException;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.RowMapper;
    import org.springframework.stereotype.Repository;
    
    import io.pivotal.workshop.snippet.domain.CrossSnippetLanguageCode;
    
    @Repository
    public class CrossSnippetLanguageCodeRepository implements SimpleRepository<CrossSnippetLanguageCode> {
    
            private final String SQL_FIND_ALL = "select * from cross_snippet_language_code";
            private final String SQL_FIND_ONE = "select * from cross_snippet_language_code where snippet_id = ?";
            private final String SQL_INSERT = "insert into cross_snippet_language_code(snippet_id,language_id,code_id) values(?,?,?)";
            private final String SQL_UPDATE = "update cross_snippet_language_code set langauge_id = ?, code_id = ? where snippet_id = ?";
            private JdbcTemplate jdbcTemplate;
    
            private final RowMapper<CrossSnippetLanguageCode> rowMapper = (ResultSet rs, int row) -> {
                    CrossSnippetLanguageCode cross = new CrossSnippetLanguageCode();
                    cross.setSnippetId(rs.getString("snippet_id"));
                    cross.setLanguageId(rs.getString("language_id"));
                    cross.setCodeId(rs.getString("code_id"));
                    return cross;
            };
    
            @Autowired
            public CrossSnippetLanguageCodeRepository(JdbcTemplate jdbcTemplate) {
                    this.jdbcTemplate = jdbcTemplate;
            }
    
            @Override
            public Iterable<CrossSnippetLanguageCode> findAll() {
                    return this.jdbcTemplate.query(SQL_FIND_ALL, rowMapper);
            }
    
            @Override
            public void saveAll(Collection<CrossSnippetLanguageCode> items) {
                    items.forEach(cross -> {
                            this.saveAll(cross);
                    });
            }
    
            @Override
            public CrossSnippetLanguageCode saveAll(CrossSnippetLanguageCode item) {
                    assert item.getSnippetId() != null;
                    assert item.getLanguageId() != null;
                    assert item.getCodeId() != null;
    
                    CrossSnippetLanguageCode cross = this.findById(item.getSnippetId());
    
                    if (cross == null) {
                            this.jdbcTemplate.update(psc -> {
                                    PreparedStatement ps = psc.prepareStatement(SQL_INSERT);
                                    ps.setString(1, item.getSnippetId());
                                    ps.setString(2, item.getLanguageId());
                                    ps.setString(3, item.getCodeId());
                                    return ps;
                            });
                            return item;
                    } else {
                            this.jdbcTemplate.update(psc -> {
                                    PreparedStatement ps = psc.prepareStatement(SQL_UPDATE);
                                    ps.setString(1, item.getLanguageId());
                                    ps.setString(2, item.getCodeId());
                                    ps.setString(3, item.getSnippetId());
                                    return ps;
                            });
                            return cross;
                    }
    
            }
    
            @Override
            public CrossSnippetLanguageCode findById(String id) {
                    try {
                            return this.jdbcTemplate.queryForObject(SQL_FIND_ONE, new Object[] { id }, rowMapper);
                    } catch (EmptyResultDataAccessException ex) {
                            return null;
                    }
            }
    
    }

    Have you noticed that the CrossSnippetLanguageCodeRepository is using a declared Java 8 lambda notation for the RowMapper?

  13. Talking about RowMapper, all the above code is using it to map the ResultSet with the domain class. Create the following mappers in the io.pivotal.workshop.snippet.repository.mapper package:

    io.pivotal.workshop.snippet.repository.mapper.LanguageRowMapper.java
    package io.pivotal.workshop.snippet.repository.mapper;
    
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import org.springframework.jdbc.core.RowMapper;
    
    import io.pivotal.workshop.snippet.domain.Language;
    
    public class LanguageRowMapper implements RowMapper<Language> {
    
            @Override
            public Language mapRow(ResultSet rs, int rowNum) throws SQLException {
                     Language lang = new Language();
                     lang.setId(rs.getString("id"));
                     lang.setName(rs.getString("name"));
                     lang.setSyntax(rs.getString("syntax"));
                    return lang;
            }
    
    }
    io.pivotal.workshop.snippet.repository.mapper.CodeRowMapper.java
    package io.pivotal.workshop.snippet.repository.mapper;
    
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import org.springframework.jdbc.core.RowMapper;
    
    import io.pivotal.workshop.snippet.domain.Code;
    
    public class CodeRowMapper implements RowMapper<Code> {
    
            @Override
            public Code mapRow(ResultSet rs, int rowNum) throws SQLException {
                    Code code = new Code();
                    code.setId(rs.getString("id"));
                    code.setSource(rs.getString("source"));
                    return code;
            }
    
    }
    io.pivotal.workshop.snippet.repository.mapper.SnippetRowMapper.java
    package io.pivotal.workshop.snippet.repository.mapper;
    
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import org.springframework.jdbc.core.RowMapper;
    
    import io.pivotal.workshop.snippet.domain.Code;
    import io.pivotal.workshop.snippet.domain.Language;
    import io.pivotal.workshop.snippet.domain.Snippet;
    
    public class SnippetRowMapper implements RowMapper<Snippet> {
    
            @Override
            public Snippet mapRow(ResultSet rs, int rowNum) throws SQLException {
                    Language lang = new Language();
                    lang.setId(rs.getString("lang_id"));
                    lang.setName(rs.getString("lang_name"));
                    lang.setSyntax(rs.getString("lang_syntax"));
    
                    Code code = new Code();
                    code.setId(rs.getString("code_id"));
                    code.setSource(rs.getString("code_source"));
    
                    Snippet snippet = new Snippet();
                    snippet.setId(rs.getString("id"));
                    snippet.setTitle(rs.getString("title"));
                    snippet.setDescription(rs.getString("description"));
                    snippet.setKeywords(rs.getString("keywords"));
                    snippet.setCreated(rs.getDate("created"));
                    snippet.setModified(rs.getDate("modified"));
                    snippet.setLang(lang);
                    snippet.setCode(code);
    
                    return snippet;
            }
    
    }
  14. The SnippetController and the SnippetConfiguration classes must be the same. Practically there is no change on any of them, they should work.

  15. Next add the schema.sql in the src/main/resources/ folder.

    DROP TABLE IF EXISTS snippet;
    CREATE TABLE snippet
    (
        id varchar(36) NOT NULL,
        title varchar(200) NOT NULL,
        keywords varchar(250) DEFAULT NULL,
        description varchar(500) DEFAULT NULL,
        created date NOT NULL,
        modified date NOT NULL,
        PRIMARY KEY (id)
    );
    
    
    DROP TABLE IF EXISTS language;
    CREATE TABLE language
    (
        id varchar(36) NOT NULL,
        name varchar(250) NOT NULL,
        syntax varchar(250) DEFAULT NULL,
        PRIMARY KEY (id)
    );
    
    DROP TABLE IF EXISTS code;
    CREATE TABLE code
    (
        id varchar(36) NOT NULL,
        source varchar(5000) NOT NULL,
        PRIMARY KEY (id)
    );
    
    
    DROP TABLE IF EXISTS cross_snippet_language_code;
    CREATE TABLE cross_snippet_language_code
    (
        snippet_id varchar(36) NOT NULL,
        language_id varchar(36) NOT NULL,
        code_id varchar(36) NOT NULL,
        PRIMARY KEY (snippet_id)
    );
Tip
Before you run your application make sure to have copied resources/static and resources/templates files from the previous labs.

Challenges

  • Run the application and make sure it works.

  • Do any necessary change to have the home page working.

    Figure 1.1: Code Snippet Manager JDBC - http://localhost:8080/

    Code Snippet Manager

Code Snippet Manager JPA

You will continue with the Code Snippet Manager code, but this time using JPA. You will reuse the code from previous labs.

  1. Open a browser and hit the url: http://start.spring.io

  2. Click the Switch to the full version link.

  3. Fill out the Code Snippet Manager JPA Project metadata with (See Figure 2.0):

    Table 2. Code Snippet Manager JPA App - metadata
    Property Value

    Group:

    io.pivotal.workshop

    Artifact:

    code-snippet-manager-jpa

    Name:

    code-snippet-manager-jpa

    Package Name:

    io.pivotal.workshop.snippet

    Dependencies:

    Web, DevTools, Groovy Templates, JPA, H2, MySQL

    Spring Boot:

    2.0.0.M7

    Figure 2.0: Spring Initializr - http://start.spring.io/

    SpringInitializr

    Tip
    You can choose either Maven or Gradle project types.
  4. type Web, DevTools, Groovy Templates, JPA, H2, and MySQL in the Dependencies field and press Enter.

  5. Click the Generate Project button.

  6. Unzip the file in any directory you want.

  7. Import your project in any IDE you want.

  8. Copy all the packages (with code) into the new project.

  9. The classes in io.pivotal.workshop.snippet.domain: Code, Language and Snippet will change because you will use the JPA features. Create/modify the following classes:

    io.pivotal.workshop.snippet.domain.Language.java
    package io.pivotal.workshop.snippet.domain;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    
    import org.hibernate.annotations.GenericGenerator;
    
    @Entity
    public class Language {
    
            @Id
            @GeneratedValue(generator = "system-uuid")
            @GenericGenerator(name = "system-uuid", strategy = "uuid")
            private String id;
            private String name;
            private String syntax = "text";
    
            public Language() {
            }
    
            public Language(String name) {
                    this();
                    this.name = name;
            }
    
            public Language(String name, String syntax) {
                    this(name);
                    this.syntax = syntax;
            }
    
            public String getId() {
                    return id;
            }
    
            public void setId(String id) {
                    this.id = id;
            }
    
            public String getName() {
                    return name;
            }
    
            public void setName(String name) {
                    this.name = name;
            }
    
            public String getSyntax() {
                    return syntax;
            }
    
            public void setSyntax(String syntax) {
                    this.syntax = syntax;
            }
    }
    io.pivotal.workshop.snippet.domain.Code.java
    package io.pivotal.workshop.snippet.domain;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    
    import org.hibernate.annotations.GenericGenerator;
    
    @Entity
    public class Code {
    
            @Id
            @GeneratedValue(generator = "system-uuid")
            @GenericGenerator(name = "system-uuid", strategy = "uuid")
            private String id;
            private String source;
    
            public Code() {
            }
    
            public Code(String source) {
                    this();
                    this.source = source;
            }
    
            public String getId() {
                    return id;
            }
    
            public void setId(String id) {
                    this.id = id;
            }
    
            public String getSource() {
                    return source;
            }
    
            public void setSource(String source) {
                    this.source = source;
            }
    
    }
    io.pivotal.workshop.snippet.domain.Snippet.java
    package io.pivotal.workshop.snippet.domain;
    
    import java.util.Date;
    
    import javax.persistence.CascadeType;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.OneToOne;
    import javax.persistence.PrePersist;
    import javax.persistence.PreUpdate;
    
    import org.hibernate.annotations.GenericGenerator;
    
    @Entity
    public class Snippet {
    
            @Id
            @GeneratedValue(generator = "system-uuid")
            @GenericGenerator(name = "system-uuid", strategy = "uuid")
            private String id;
            private String title;
            private String keywords = "";
            private String description = "";
    
            @OneToOne(fetch=FetchType.EAGER, cascade = CascadeType.ALL)
            private Language lang;
    
            @OneToOne(fetch=FetchType.EAGER, cascade = CascadeType.ALL)
            private Code code;
    
            @Column(insertable = true, updatable = false)
            private Date created;
            private Date modified;
    
            public Snippet() {
                    this.id = java.util.UUID.randomUUID().toString().replaceAll("-", "");
                    this.created = new Date();
                    this.modified = new Date();
            }
    
            public Snippet(String title, String keywords, String description, Language lang, Code code) {
                    this();
                    this.title = title;
                    this.keywords = keywords;
                    this.description = description;
                    this.lang = lang;
                    this.code = code;
            }
    
            public Snippet(String title, Language lang, Code code) {
                    this(title, "", "", lang, code);
            }
    
            public String getId() {
                    return id;
            }
    
            public void setId(String id) {
                    this.id = id;
            }
    
            public String getTitle() {
                    return title;
            }
    
            public void setTitle(String title) {
                    this.title = title;
            }
    
            public Language getLang() {
                    return lang;
            }
    
            public void setLang(Language lang) {
                    this.lang = lang;
            }
    
            public String getDescription() {
                    return description;
            }
    
            public void setDescription(String description) {
                    this.description = description;
            }
    
            public String getKeywords() {
                    return keywords;
            }
    
            public void setKeywords(String keywords) {
                    this.keywords = keywords;
            }
    
            public Code getCode() {
                    return code;
            }
    
            public void setCode(Code code) {
                    this.code = code;
            }
    
            public Date getCreated() {
                    return created;
            }
    
            public void setCreated(Date created) {
                    this.created = created;
            }
    
            public Date getModified() {
                    return modified;
            }
    
            public void setModified(Date modified) {
                    this.modified = modified;
            }
    
            @PrePersist
            void onCreate() {
                    this.setCreated(new Date());
                this.setModified(new Date());
            }
    
            @PreUpdate
            void onUpdate() {
                    this.setModified(new Date());
            }
    }

    See that you are using now the JPA standard annotations: @Entity, @Id, @OneToOne, etc.

  10. The repositories will change becasue you will use the power of the spring-data and spring-data-jpa projects. Create/Modify the following classes:

    io.pivotal.workshop.snippet.repository.LanguageRepository.java
    package io.pivotal.workshop.snippet.repository;
    
    import org.springframework.data.repository.CrudRepository;
    
    import io.pivotal.workshop.snippet.domain.Language;
    
    public interface LanguageRepository extends CrudRepository<Language, String> {
    
    }
    io.pivotal.workshop.snippet.repository.CodeRepository.java
    package io.pivotal.workshop.snippet.repository;
    
    import org.springframework.data.repository.CrudRepository;
    
    import io.pivotal.workshop.snippet.domain.Code;
    
    public interface CodeRepository extends CrudRepository<Code, String> {
    
    }
    io.pivotal.workshop.snippet.repository.SnippetRepository.java
    package io.pivotal.workshop.snippet.repository;
    
    import org.springframework.data.repository.CrudRepository;
    
    import io.pivotal.workshop.snippet.domain.Snippet;
    
    public interface SnippetRepository extends CrudRepository<Snippet, String> {
    
    }

    As you can see from the code above, you don’t need to implement anything but just extend from the CrudRepository<T,ID> interface, which uses generics, meaning that you need to pass the domain class and the unique identifies, in this case what is marked as @Id. All the operation from the CrudRepository<T,ID> interface will be implemented by the spring-data classes.

  11. The SnippetController and the SnippetConfiguration classes must be the same. Practically there is no change on any of them, they should work.

  12. This time there is no need for any SQL schema, this time you will use the DDL auto-creation feature. In the src/main/resources/application.properties file add the following content:

    src/main/resources/application.properties
    ## JPA
    spring.jpa.generate-ddl=true
    spring.jpa.hibernate.ddl-auto=create-drop
Tip
Before you run your application make sure to have copied resources/static and resources/templates files from the previous labs.

Challenges

  • Run the application and make sure it works.

  • Do any necessary change to have the home page working.

    Figure 1.1: Code Snippet Manager JPA - http://localhost:8080/

    Code Snippet Manager JPA

Code Snippet Manager JPA REST

You will continue with the Code Snippet Manager code, but this time using Data Rest module. You will reuse the code from previous labs.

  1. Open a browser and hit the url: http://start.spring.io

  2. Click the Switch to the full version link.

  3. Fill out the Code Snippet Manager JPA REST Project metadata with (See Figure 3.0):

    Table 3. Code Snippet Manager JPA REST App - metadata
    Property Value

    Group:

    io.pivotal.workshop

    Artifact:

    code-snippet-manager-jpa-rest

    Name:

    code-snippet-manager-jpa-rest

    Package Name:

    io.pivotal.workshop.snippet

    Dependencies:

    Web, DevTools, Groovy Templates, JPA, Rest Repositories, H2, MySQL

    Spring Boot:

    2.0.0.M7

    Figure 3.0: Spring Initializr - http://start.spring.io/

    SpringInitializr

    Tip
    You can choose either Maven or Gradle project types.
  4. type Web, DevTools, Groovy Templates, JPA, Rest Repositories, H2, and MySQL in the Dependencies field and press Enter.

  5. Click the Generate Project button.

  6. Unzip the file in any directory you want.

  7. Import your project in any IDE you want.

  8. Copy all the packages (with code) into the new project.

  9. The only class that will change will be the SnippetController. Create/modify the folowing class:

    io.pivotal.workshop.snippet.controller.SnippetController.java
    package io.pivotal.workshop.snippet.controller;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.servlet.ModelAndView;
    
    import io.pivotal.workshop.snippet.repository.LanguageRepository;
    import io.pivotal.workshop.snippet.repository.SnippetRepository;
    
    @RestController
    public class SnippetController {
    
    
            private SnippetRepository snippetRepository;
            private LanguageRepository languageRepository;
    
            public SnippetController(SnippetRepository snippetRepository,LanguageRepository languageRepository){
                    this.snippetRepository = snippetRepository;
                    this.languageRepository = languageRepository;
            }
    
            @RequestMapping("/")
            public ModelAndView home(){
                    assert snippetRepository != null;
    
                    Map<String,Object> model = new HashMap<String,Object>();
                    model.put("langs", languageRepository.findAll());
                    model.put("snippets", snippetRepository.findAll());
    
                    return new ModelAndView("views/home",model);
            }
    
    }

    As you can see, only is necessary the home method, and this is because the spring-data-rest module will take care of creating all the web controllers for accepting any request regarding to the domain repositories.

  10. When using the spring-data-rest libraries, the default path for the controllers is the root: /, but in this case you are using the root as home page, that’s why is necessary to override the spring-data-rest context path defaults, in the src/main/resources/application.properties file add the following:

    src/main/resources/application.properties
    ## JPA
    spring.jpa.generate-ddl=true
    spring.jpa.hibernate.ddl-auto=create-drop
    
    ## REST
    spring.data.rest.base-path=api

    Now you should be able to use the http://localhost:8080/api to access the REST repositories.

Tip
Before you run your application make sure to have copied resources/static and resources/templates files from the previous labs.

Challenges

  • Run the application and make sure it works.

    Tip
    See the logs and take a look at all the mapping that is being generated.
  • Do any necessary change to have the home page working.

    Figure 3.1: Code Snippet Manager JPA REST - http://localhost:8080/

    Code Snippet Manager

  • Go to the http://localhost:8080/api, you should see the following image:

    Figure 3.2: Code Snippet Manager JPA REST - http://localhost:8080/api

    Code Snippet Manager API

    and if you click the http://localhost:8080/api/snippets you should see the following image:

    Figure 3.2: Code Snippet Manager JPA REST - http://localhost:8080/api/snippets

    Code Snippet Manager API

  • Review all the links and see that the all the information is being translated into a JSON/HAL.

  • So far we are using the H2 engine, do the necessary changes to use now MySQL, how can you accomplish this?

HOMEWORK

  • Use multiple databases like MySQL and MongoDB (multiple Datasources).

  • Use Flyway or Liquibase to do a Database migration. Add an executeCommand field to the Code domain class. This field will tell how to execute the source code provided.

  • Use jOOQ framework.