###### tags: `Java` `MVC` `SpringBoot` # Spring Boot + MVC Startup After creating a New Spring Project in Spring Boot (with version 2.7.10) - [ ] 1. Add following dependencies to pom.xml ``` xml! <!-- ADDED DEPENDENCIES --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId> <version>0.46</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!-- BOOTSTRAP DEPENDENCIES --> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>5.2.3</version> </dependency> ``` If using a database, be sure to also add ```xml! <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> ``` If doing login and registration, be sure to also add ```xml! <!-- BCRYPT --> <dependency> <groupId>org.mindrot</groupId> <artifactId>jbcrypt</artifactId> <version>0.4</version> </dependency> ``` :::info If the pom.xml file is having an issue, try changing 2.7.10 to 2.7.9 or 2.7.8 ::: - [ ] 2. Add a new folder to **src/main/webapp** called **WEB-INF** ![](https://i.imgur.com/G2Zaay7.png) :::info We'll be coming back to this folder later. ::: - [ ] 3. In **src/main/resources**, add the following line to **application.properties** ```properties! spring.mvc.view.prefix=/WEB-INF/ ``` **If using a database**, also add the following lines ```properties! spring.datasource.url=jdbc:mysql://localhost:3306/<YOUR_SCHEMA> spring.datasource.username=root spring.datasource.password=root spring.jpa.hibernate.ddl-auto=update spring.mvc.hiddenmethod.filter.enabled=true ``` :::danger If using a database, be sure to create the database in MySQL workbench before adding any additional files. Be sure to have the same name for the database as <YOUR_SCHEMA> was replaced by. ::: :::info The hidden method filter enabler is so that you can use a put method in editing contents from the database ::: - [ ] 4. *Right-click* on **src/main/java/com.*match.folder.name.here*** and create a *new > package*. Name this package com..controllers ![](https://i.imgur.com/2zdKT4b.png) Repeat this process for the following packages. * .models * .repositories * .services - [ ] 5. In the controllers folder, create a *new > class* for the Controller :::danger If using the code snippet, be sure not to get rid of any auto-generated packages ::: ```java! @Controller public class HomeController { //@Autowired //private <SERVICE_NAME> <service_name>; @GetMapping("/") // reserve route public String index() { return "index.jsp"; // map route to jsp } } ``` This will be where the route paths are reserved and mapped to the appropriate .jsp file. :::warning If getting red lined errors, be sure to try Ctrl+Shift+o (windows) or ⌘+Shift+o (mac) ::: - [ ] 6. In the WEB-INF folder created earlier, *right-click* on the folder, select **new > other...**, and search for *jsp*. ![](https://i.imgur.com/zrrr5O3.png) Create the .jsp file for the route path. This will be where your html and jsp code for the page go. :::warning If you copied the HomeController, be sure to name this file *index.jsp* ::: - [ ] 7. Copy the following code into your .jsp file created. ```jsx! <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html> <html> <head> <!-- Bootstrap CSS --> <link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.min.css"> <!-- CSS only --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous"> <!-- My CSS --> <link rel='stylesheet' href='/css/styles.css'> <!-- JS for Bootstrap / jQuery --> <script src='/webjars/jquery/jquery.min.js'> </script> <script src='/webjars/bootstrap/js/boostrap.min.js'> </script> <!-- JavaScript Bundle with Popper --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"> </script> <!-- My JS --> <script type = "text/javascript" src='/js/scripts.js'></script> <meta charset="UTF-8"> <!-- Title --> <title>Project Title</title> </head> <body> <!-- HEADER --> <header> <h1>Testing</h1> <nav> </nav> </header> <!-- MAIN --> <main> </main> <!-- FOOTER --> <footer> </footer> </body> </html> ``` - [ ] 8. The application is now good to launch with Spring Boot. # Java MVC ## Trouble Shooting - [Java Debugging Notes](https://hackmd.io/4dzTOgrRRnCOFWmZbyFfyQ?view) ## Comments ### Java Classes - Attributes Label ``` // ========================== // ATTRIBUTES // ========================== ``` - Constructor Label ``` // ========================== // CONSTRUCTOR // ========================== ``` - Getter and Setter Label ``` // ========================== // GETTERS / SETTERS // ========================== ``` - Method Label ``` // ========================== // METHODS // ========================== ``` - Interface Label ``` // ========================== // INTERFACE // ========================== ``` - Relationship Label ``` // ========================== // RELATIONSHIPS // ========================== ``` ## Quick Code ### Debugging - Print out the Variables ```java! System.out.println("=".repeat(20)); System.out.printf("\n\tVARIABLES:\t \n\n"); System.out.println("=".repeat(20)); ``` - Print line number of cleared code ```java! System.out.println("=".repeat(20)); System.out.println("\n\tCLEARED [Line: <LINE_NUMBER>]\t \n\n"); System.out.println("=".repeat(20)); ``` ### Models The model is like the blueprint: The blueprint provides the detailed plan for how the house should be built. In Java Spring, the model represents the data structure and defines how data is stored in the database. - Basic setup :::info To create a new file, add a *New > Class* to the .models package ::: ```java! @Entity @Table(name="<model_plural>") public class <MODEL_NAME> { // ========================== // ATTRIBUTES // ========================== // create unique id @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // This will not allow the createdAt column to be updated after creation @Column(updatable=false) @DateTimeFormat(pattern="yyyy-MM-dd") private Date createdAt; @DateTimeFormat(pattern="yyyy-MM-dd") private Date updatedAt; // ========================== // CONSTRUCTOR // ========================== public <MODEL_NAME>(){} // ========================== // GETTERS / SETTERS // ========================== @PrePersist protected void onCreate(){ this.createdAt = new Date();} @PreUpdate protected void onUpdate(){ this.updatedAt = new Date();} // add getters/setters for ALL attributes // **HERE** } ``` :::warning When adding the imports (Ctrl+Shift+o / ⌘+Shift+o) for everything BUT Date, make sure to select the javax.persistence option. For Date, select the java.util option. ::: - Relationships :::danger Be sure to add the Getter and Setter for the new relationship variable added. ::: - One-to-One ```java! // One-to-One Dominate Side // <THIS_MODEL> ---- <ATTACHED_MODEL> @OneToOne(mappedBy="<this_model>", cascade=CascadeType.ALL, fetch=FetchType.LAZY) private <ATTACHED_MODEL> <ATTACHED_MODEL>; ``` ```java! // One-to-One Attached Side // <ATTACHED_MODEL> ---- <THIS_MODEL> @OneToOne(fetch=FetchType.LAZY) @JoinColumn(name="<attached_model>_id") private <ATTACHED_MODEL> <attached_model>; ``` - One-to-Many ```java! // One-to-Many // <THIS_MODEL> ---< <ATTACHED_MODEL> @OneToMany(mappedBy="<this_model>", cascade=CascadeType.ALL, fetch = FetchType.LAZY) private List<<ATTACHED_MODEL>> <attched_plural>; ``` ```java! // Many-to-One // <THIS_MODEL> >--- <ATTACHED_MODEL> @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="<attached_model>_id") private <ATTACHED_MODEL> <attached_model>; ``` - Many-to-Many - In both models ```java! // Many-to-Many // <THIS_MODEL> >-- <TABLE_MODEL> --< <ATTACHED_MODEL> @OneToMany(mappedBy="<this_model>", fetch=FetchType.LAZY) private List<<TABLE_MODEL>> <attached_plural>; ``` - In the table model ```java! // Many-to-Many // <ATTACHED_MODEL_1> >-- <THIS_TABLE_MODEL> --< <ATTACHED_MODEL_2> @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="<attached_model_1>_id") private <ATTACHED_MODEL_1> <attached_model_1>; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="<attached_model_2>_id") private <ATTACHED_MODEL_2> <attached_model_2>; ``` ### Repository The repository is like the building materials: The building materials are the resources needed to construct the house, such as wood, concrete, and nails. In Java Spring, the repository is responsible for accessing and storing data in the database. - Basic Setup :::success To create a new file, add a *New > Interface* to the .repositories package ::: ```java! @Repository public interface <MODEL_NAME>Repo extends CrudRepository<<MODEL_NAME>, Long> { // Model gets imported here List<<MODEL_NAME>> findAll(); // No need to add .save here because CrudRepository already has it // Repo gets "exported" to model Service } ``` ### Services The service is like the project manager: The project manager oversees the construction process, ensures that everything is on track, and communicates with different teams. In Java Spring, the service acts as an intermediary between the controller and the repository. It handles business logic and performs operations on the data. - Basic Setup with CRUD :::info To create a new file, add a *New > Class* to the .services package ::: ```java! @Service public class <MODEL_NAME>Serv { @Autowired private <REPO_NAME> <repo_name>; // ========================== // CRUD METHODS // ========================== // create public <MODEL_NAME> createOne(<MODEL_NAME> i) { return <repo_name>.save(i); } // read all public List<<MODEL_NAME>> getAll() { return <repo_name>.findAll(); } // read one public <MODEL_NAME> getOne(Long id) { return <repo_name>.findById(id).orElse(null); } // update public <MODEL_NAME> updateOne(<MODEL_NAME> i) { return <repo_name>.save(i); } // delete public void deleteOne(Long id) { <repo_name>.deleteById(id); } } ``` ### Controllers The controller is like the homeowner: The homeowner has the final say over how the house should look and function. In Java Spring, the controller receives requests from the user and communicates with the service to perform operations on the data. It then returns a response to the user. - Basic setup :::info To create a new file, add a *New > Class* to the .controllers package ::: ```java! @Controller public class <MODEL_NAME>Controller { @Autowired private <SERVICE_NAME> <service_name>; @GetMapping("/") public String <PAGE_NAME>() { return "<PAGE_NAME>.jsp"; } } ``` - Get method with a form ```java! @GetMapping("/<ROUTE>") public String <PAGE_NAME>(Model model) { // get attribute from server // add attribute to model return "<PAGE_NAME>.jsp"; } ``` - To check if user is logged in ```java! if(session.getAttribute("userID") == null) { return "redirect:/logReg"; } ``` - Post method for a form ```java! @PostMapping("/<ROUTE>") public String create<MODEL_NAME>(@Valid @ModelAttribute("modelForm") <MODEL_NAME> <model_name>, BindingResult result, Model model) { if(result.hasErrors()) { // get attribute from server // add attribute to model return "<PAGE_NAME>.jsp"; } else { <service_name>.createOne(<model_name>); return "redirect:/"; } } ``` :::warning For **edits**, change the *PostMapping* to **PutMapping** ::: ### JSP #### Basic Setup - Main beginning template ```jsx! <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html> <html> <head> <!-- Bootstrap CSS --> <link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.min.css"> <!-- CSS only --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous"> <!-- My CSS --> <link rel='stylesheet' href='/css/styles.css'> <!-- JS for Bootstrap / jQuery --> <script src='/webjars/jquery/jquery.min.js'> </script> <script src='/webjars/bootstrap/js/boostrap.min.js'> </script> <!-- JavaScript Bundle with Popper --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"> </script> <!-- My JS --> <script type = "text/javascript" src='/js/scripts.js'></script> <meta charset="UTF-8"> <!-- Title --> <title>Project Title</title> </head> <body> <!-- HEADER --> <header> <nav> </nav> </header> <!-- MAIN --> <main> </main> <!-- FOOTER --> <footer> </footer> </body> </html> ``` #### Java Values - Print ```jsx! <c:out value="${}"/> ``` - To forEach loop ```jsx! <c:forEach items="${}" var="i"> </c:forEach> ``` - In **items**: name of list you want to loop through. - EX: items="${allItems}" - In **var**: the iteratior value to access individual model attributes. - EX: var="i" - `<p> ${i.price} </p> // in the forEach loop` :::info Notice how there is no c:out needed for printing the variable, as it still counts as being inside a jsp function since the forEach loop has not ended yet. ::: - Conditionals ```jsx! <c:if test="${ }"> </c:if> ``` :::info There are no else if or else statements in jsp. (Use another c:if with the inverse of the first test for the second one) ::: #### Forms - For a more in depth look into forms: - [Spring MVC From Tutorial - Baeldung](https://www.baeldung.com/spring-mvc-form) - At the top of the jsp page, add the following ```! <!-- for forms --> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix = "fmt" uri = "http://java.sun.com/jsp/jstl/fmt" %> <!-- for validation --> <%@ page isErrorPage="true" %> ``` - Basic form set up with validations ```jsx! <form:form action="/" method="post" modelAttribute="modelForm" class="d-flex"> <button>Submit</button> </form:form> ``` :::warning If the form is for an **Edit** PostMapping, add the following in the form ::: ```jsx! <input type="hidden" name="_method" value="put"> ``` - Show Error ```jsx! <!-- Validation Error --> <form:errors path="<attribute_name>" class="text-warning"/> ``` - String Variable ```jsx! <!-- Attribute Information --> <div class="d-flex"> <label for="<attribute_name>">LABEL</label> <input type="text" name="<attribute_name>"> </div> ``` - Integer Variable ```jsx! <!-- Attribute Information --> <div class="d-flex"> <label for="<attribute_name>">LABEL</label> <input type="number" step="1" name="<attribute_name>"> </div> ``` #### Table (w/ bootstrap) - Basic Setup ```jsx! <table class="table"> <thead> <!--- Column Labels ---> <tr> <th scope="col">1</th> <th scope="col">2</th> <th scope="col">3</th> <th scope="col">4</th> </tr> </thead> <tbody> <!--- Row Data ---> <tr> <th scope="row">A</th> <td>B</td> <td>C</td> <td>D</td> </tr> </tbody> </table> ``` #### Additional Inputs - For inputting own style sheet named style.css ```jsx! <link rel="stylesheet" type="text/css" href="/css/style.css"> ``` - For inputting a js file ```jsx! <script type="text/javascript" src="/js/app.js"></script> ``` ### Login and Registration #### Models - User ```java! @Entity @Table(name="users") public class User { // ========================== // ATTRIBUTES // ========================== // create unique id @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank(message="Name is required!") @Size(min=3, max=30, message="First Name must be between 3 and 30 characters") private String userName; @NotEmpty(message="Email is required!") @Email(message="Please enter a valid email!") private String email; @NotEmpty(message="Password is required!") @Size(min=8, max=128, message="Password must be between 8 and 128 characters") private String password; @Transient // don't go to the db @NotEmpty(message="Confirm Password is required!") @Size(min=8, max=128, message="Confirm Password must be between 8 and 128 characters") private String confirm; // This will not allow the createdAt column to be updated after creation @Column(updatable=false) @DateTimeFormat(pattern="yyyy-MM-dd") private Date createdAt; @DateTimeFormat(pattern="yyyy-MM-dd") private Date updatedAt; // ========================== // RELATIONSHIPS // ========================== // ========================== // CONSTRUCTOR // ========================== public User(){} // ========================== // GETTERS / SETTERS // ========================== @PrePersist protected void onCreate(){ this.createdAt = new Date();} public Date getCreatedAt() { return createdAt; } public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; } @PreUpdate protected void onUpdate(){ this.updatedAt = new Date();} public Date getUpdatedAt() { return updatedAt; } public void setUpdatedAt(Date updatedAt) { this.updatedAt = updatedAt; } // add getters/setters for ALL attributes public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getConfirm() { return confirm; } public void setConfirm(String confirm) { this.confirm = confirm; } } ``` - LoginUser ```java! public class LoginUser { @NotEmpty(message="Email is required!") @Email(message="Please enter a valid email!") private String email; @NotEmpty(message="Password is required!") @Size(min=8, max=128, message="Password must be between 8 and 128 characters") private String password; public LoginUser() {} public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } ``` #### Repository - User ```java! @Repository public interface UserRepo extends CrudRepository<User, Long> { // Model gets imported here List<User> findAll(); // probably not going to be used unless admin Optional<User> findByEmail(String email); // No need to add .save here because CrudRepository already has it // Repo gets "exported" to model Service } ``` #### Service - User ```java! @Service public class UserServ { @Autowired private UserRepo userRepo; // ========================== // REGISTRATION // ========================== public User register(User newUser, BindingResult result) { // if email already in use if(userRepo.findByEmail(newUser.getEmail()).isPresent()) { result.rejectValue("email", "Unique", "This email is already in use!"); } // if the passwords do not match if(!newUser.getPassword().equals(newUser.getConfirm())) { result.rejectValue("confirm", "Matches", "The Confirm Password must match Password!"); } // where there errors in the last checks or form? if(result.hasErrors()) { return null; } else { // encrypting the password String hashed = BCrypt.hashpw(newUser.getPassword(), BCrypt.gensalt()); newUser.setPassword(hashed); return userRepo.save(newUser); } } // ========================== // LOGIN // ========================== public User login(LoginUser newLogin, BindingResult result) { // is the email valid? Optional<User> potentialUser = userRepo.findByEmail(newLogin.getEmail()); if(!potentialUser.isPresent()) { result.rejectValue("email", "Unique", "Unknown email!"); return null; } // if the passwords do not match User user = potentialUser.get(); if(!BCrypt.checkpw(newLogin.getPassword(), user.getPassword())) { result.rejectValue("password", "Matches", "Invalid Password!"); } // where there errors in the last checks or form? if(result.hasErrors()) { return null; } else { return user; } } // ========================== // CRUD METHODS // ========================== // create public User createOne(User i) { return userRepo.save(i); } // read all public List<User> getAll() { return userRepo.findAll(); } // read one public User getOne(Long id) { return userRepo.findById(id).orElse(null); } // update public User updateOne(User i) { return userRepo.save(i); } // delete public void deleteOne(Long id) { userRepo.deleteById(id); } } ``` #### Controller - User ```java! @Controller public class UserController { @Autowired private UserServ userServ; @GetMapping("/logReg") public String logReg(Model model, HttpSession session){ // check for session if(session.getAttribute("userID") != null) { return "redirect:/dashboard"; } // Bind empty User and LoginUser objects to the JSP // to capture the form input model.addAttribute("newUser", new User()); model.addAttribute("newLogin", new LoginUser()); return "logReg.jsp"; } @PostMapping("/register") public String register(@Valid @ModelAttribute("newUser") User newUser, BindingResult result, Model model, HttpSession session) { // call a register method in the service // to do some extra validations and create a new user! userServ.register(newUser, result); if(result.hasErrors()) { // Be sure to send in the empty LoginUser before // re-rendering the page. model.addAttribute("newLogin", new LoginUser()); return "logReg.jsp"; } // No errors! // Store their ID from the DB in session, i.e. log them in. session.setAttribute("userID", newUser.getId()); return "redirect:/"; } @PostMapping("/login") public String login(@Valid @ModelAttribute("newLogin") LoginUser newLogin, BindingResult result, Model model, HttpSession session) { // Add once service is implemented: User user = userServ.login(newLogin, result); if(result.hasErrors()) { model.addAttribute("newUser", new User()); return "logReg.jsp"; } // No errors! // Store their ID from the DB in session, i.e. log them in. session.setAttribute("userID", user.getId()); return "redirect:/"; } // logout @GetMapping("/logout") public String logout(HttpSession session) { session.invalidate(); return "redirect:/"; } } ``` #### JSP - Registration ```jsx! <form:form action="/register" method="POST" modelAttribute="newUser" class="mx-5 my-2"> <div class="border text-center py-3"> <h3>Register</h3> </div> <div class="border py-2"> <section> <form:label path="userName" class="border-end w-50 ps-2 me-1">User Name:</form:label> <form:input type="text" class="input" path="userName" /> </section> <form:errors path="userName" class="text-danger ps-2" /> </div> <div class="border py-2"> <section> <form:label path="email" class="border-end w-50 ps-2 me-1">Email:</form:label> <form:input type="email" class="input" path="email" /> </section> <form:errors path="email" class="text-danger ps-2" /> </div> <div class="border py-2"> <section> <form:label path="password" class="border-end w-50 ps-2 me-1">Password:</form:label> <form:input type="password" class="input" path="password" /> </section> <form:errors path="password" class="text-danger text-right" /> </div> <div class="border py-2"> <section> <form:label path="confirm" class="border-end w-50 ps-2 me-1">Confirm Password:</form:label> <form:input type="password" class="input" path="confirm" /> </section> <form:errors path="confirm" class="text-danger ps-2" /> </div> <button class="btn btn-secondary my-1 w-100">Register</button> </form:form> ``` - Login ```jsx! <form:form action="/login" method="POST" modelAttribute="newLogin" class="mx-5 mt-3"> <div class="border text-center py-3"> <h3>Login</h3> </div> <div class="border py-2"> <section> <form:label path="email" class="border-end w-50 ps-2 me-1">Email</form:label> <form:input type="email" class="input" path="email" /> </section> <form:errors path="email" class="text-danger" /> </div> <div class="border py-2"> <section> <form:label path="password" class="border-end w-50 ps-2 me-1">Password</form:label> <form:input type="password" class="input" path="password" /> </section> <form:errors path="password" class="text-danger" /> </div> <button class="btn btn-primary my-1 w-100">Login</button> </form:form> ```