You will once again work with the Tech Jobs application. This time around you’ll add ORM
functionality by using Spring Data. You will be responsible for completing the code to allow users
to create new job data.
Your final application will have the same list and search capabilities as your Tech Jobs (MVC Edition) but you’ll need to do the work to connect the project to a database for storing user-submitted job data.
Each of the four sections of this assignment will also ask you to demonstrate your SQL skills under an item labelled SQL TASK.
Set up a local copy of the project:
You won’t be able to run your application or the tests yet. If you try to do so, you’ll see a host of errors relating to the Spring Data annotations and classes. Some of these have already been used in the code, but the dependency that includes them has not yet been declared. That will be one of your tasks. You’ll need to complete Part1 before you can run the application and view it in a browser.
That said, it’s a good idea to scan the classes and templates even before you’re able to execute
bootRun. Take a gander at the Job class. It will look somewhat similar to the model in
Tech Jobs (MVC Edition), with a few key differences.
You’re no longer using a csv file to load job data, instead, we’ll be creating new Job objects via a user form. The Job data will be stored in a MySQL database that you’ll setup in Part 1 of this assignment.
As you explore
the starter code, you’ll notice that the JobField abstract class is no longer present. Your task for
Part 2 is to complete the work to persist some of the classes.
You’ll do this for Employer and Skill classes, as well as Job.
The Job class will also look different from how you have last seen it. In Parts 3 and 4, you’ll
add object relational mapping on the Job class by refactoring the employer and skills (formerly coreCompetency)
fields.
In your IntelliJ project, you’ll see an empty file in the root directory called queries.sql. After completing the
Java updates for parts 1,2,3, and 4, we ask you to test your application updates with SQL statements.
Since you are entering your own data, the queries we ask you to write will return unique result sets. For example, if you haven’t entered any data yet, there may be an empty result set. However, as the architect of the database, you have the knowledge to write the appropriate queries nonetheless.
Start MySQL Workbench and create a new schema named techjobs.
Tip
Remember to double click on the schema name in the file tree to make it the default schema.
In the administration tab, create a new user, techjobs with the same settings as described in
the lesson tutorial. This user should have the password techjobs as well.
Update build.gradle with the necessary dependencies. At this point, you should be able to run the tests. Run the tests in TestTaskOne to verify your gradle dependencies.
Update src/resources/application.properties with the correct info. This will include
spring.datasource.url set to the address of your database connection, as well as the username and password
for a user you have created. Refer to the tip below for the other properties you must add to complete your
database setup.
Tip
You can double check your setup against what you’ve already done for your coding events repo. You can copy these property assignments from your coding events repo, only needing to change the database address and username/password values.
Tip
If when starting your application, you encounter an error similar to
com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value 'CDT' is unrecognized …
then add ?useLegacyDatetimeCode=false&serverTimezone=America/Chicago to the end of your spring.datasource.url value.
When your database is properly configured, you should have no compiler errors when starting the application. Execute bootRun
and check the compiler output to make sure this is the case. If everything runs, you will be able to view your app
locally in the browser at Localhost:8080 (unless of course you have changed the server port).
job. In queries.sql under “Part 1”, list the columns and their data types in the table as a SQL comment.Your running application still has limited functionality. You won’t yet be able to add a job with the Add Job form. You also won’t yet be able to view the list of jobs or search for jobs - but this is mostly because you have no job data. Move on to Part 2 below to start adding these functionalities.
You will need to have completed the setup steps before starting this section.
AbstractEntity¶We’ve replaced the abstract class JobField with an even more abstracted class aptly named,
AbstractEntity. This class holds the fields and methods that are common across the Job class
and the classes it contains as fields.
AbstractEntity but not a table for this parent class. Therefore, give AbstractEntity the
@MappedSuperclass annotation.AbstractEntity will be entities themselves, add the @Id
and @GeneratedValue annotations to the field id.name field from AbstractEntity. Add appropriate
validation annotations so that:Job, Employer, and Skill classes. Some employer names might be longer than 50 characters.In the last assignment, a Job object contained string fields for employer and core competency data. This employer
and skill (formerly core competency) information about a particular job will now be stored in classes themselves.
These items themselves will hold their own supplementary information.
Open the Employer model class. In addition to the fields inherited from AbstractEntity, Employer should have a
string field for location. Add the field for location with validation that ensures it is not empty and has a reasonable length. In addition, add public accessor methods
to Employer.
Note
For the purposes of this application, an employer can only have one location.
Employer is a class that will be mapped to one of our tables. Make sure the class has the
@Entity annotation, as well as the no-arg constructor required for Hibernate to create an
object.
In the model class Skill, add a field for a longer description of the skill, named description, with public accessor methods. Some hiring managers like to have
more information available about the nature of a given programming language or framework.
As with Employer, give this class the @Entity annotation and be sure it contains a no-arg
constructor.
To map the Employer and Skill classes to your techjobs database, you’ll add data access interfaces for these relational
objects, similar to the existing JobRepository interface. Like JobRepository, make use of the Spring Data
CrudRepository interface to map our objects.
models/data, create a new interface EmployerRepository.EmployerRepository should extend CrudRepository.EmployerRepository should be annotated with @Repository.SkillRepository.Warning
The tests in TestTaskTwo relating to the following tasks have been commented out because they depend on the code you wrote earlier. Open TestTaskTwo in IntelliJ and find these tests. For each one:
import statements for some of the classes used in these methods.cmd+/ on Mac or ctrl+/ on Windows.If you do not uncomment these tests, your code will not pass the autograder.
Uncommenting these methods will introduce some new errors related to a class named SkillController, but these will be fixed by the code you are about to write. If you like, you can leave these commented out until you get to that task.
With the employer repository in place, we will reference this to send object information through
the EmployerController handlers. EmployerController contains two handlers with missing
information. Your task here is to make use of the EmployerRepository class in these handlers.
Add a private field of EmployerRepository type called employerRepository to
EmployerController. Give this field an @Autowired annotation.
Add an index method that responds at /employers with a list of all employers in the database. This method should use the template employers/index. To figure out the name of the model attribute you should use to pass employers into the view, review this template.
processAddEmployerForm already takes care of sending the form back if any of the submitted
employer object information is invalid. However, it doesn’t yet contain the code to save a
valid object. Use employerRepository and the appropriate method to do so.
displayViewEmployer will be in charge of rendering a page to view the contents of an individual
employer object. It will make use of that employer object’s id field to grab the correct
information from employerRepository. optEmployer is currently initialized to null. Replace this using
the .findById() method with the right argument to look for the given employer object from
the data layer.
Tip
The variable holding the id you want to query for is already provided for you in the controller method’s parameters.
Create a SkillController class and replicate the steps you followed above for EmployerController. The new controller should have the methods, index, displayAddSkillForm, processAddSkillForm, and displayViewSkill. These methods should behave exactly as the corresponding methods in EmployerController. The relevant templates have already been created for you.
At this point, uncomment all remaining methods in TestTaskTwo, if you have not done so already. You’ll need to add an import statement for the new controller to the test file.
The employer and skill view templates for adding and viewing these objects are made for you. Before you move on, test your application now to make sure it runs as expected. You should be able to create Employer and Skill objects and view them.
queries.sql under “Part 2”, write a query to list the names of the employers in St. Louis City. Do NOT specify an ordering for the query results.Tip
If everything seems to work – that is, you are able to submit the form without any errors – but you don’t see your employers or skills in the list after submission, here’s what you should check:
employers and skills table? Check by going to MySQL Workbench
and looking for the employer/skill data within your schema..findAll() on the repository?When everything works, move on to Part 3 below.
In this application, any one Job object is affiliated with one employer while one Employer may contain several jobs.
Now that you have set up persistence for the Employer and Skill classes, it is time to update the Job class
to make use of these. Job is already using the Spring Data framework to be persistent and now you’ll update its
Employer field to create a one-to-many relationship. You’ll also add a field on Employer to list the jobs associated
with each instance.
jobs Field to Employer¶Employer, add a private property jobs of type
List<Job> and initialize it to an empty ArrayList. After we
set up the Job class to work with Employer objects, this list
will represent the list of all items in a given job. We’ll do this
in a bit.@OneToMany and @JoinColumn annotations on the jobs list in Employer to declare the relationship between
data tables. Recall that this annotation needs a name parameter. What should its value be?Job Model¶Job model class has id and name fields, it too can inherit from AbstractEntity. Update the
class definition of Job to extend AbstractEntity. Remove the redundant fields from Job.employer to be of type Employer. You will also need to refactor the affected constructor
and getter and setter that use this field.@ManyToOne annotation on the field employerHomeController¶Warning
As above, there is a test in TestTaskThree that needs to be uncommented. There is only one, and it is near the end of the file. Do that now.
Open TestTaskThree in IntelliJ and find this test.
cmd+/ on Mac or ctrl+/ on Windows.If you do not uncomment this test, your code will not pass the autograder.
We’ll make several updates here. Similar to what you have done in Part 1, several of the methods in HomeController are
missing code because the class has not yet been wired with the data layer yet.
Add a field employerRepository annotated with @Autowired.
A user will select an employer when they create a job. Add the employer data from employerRepository into the form template.
The add job form already includes an employer selection option. Be sure your variable name for the employer data matches that
already used in templates/add.
Checkout templates/add.html. Make a mental note of the name of the variable being used to pass the selected employer
id on form submission.
In processAddJobForm, add code inside of this method to select the employer object that has been chosen to be
affiliated with the new job. You will need to select the employer using the request parameter you’ve added to the method.
Note
An employer only needs to be found and set on the new job object if the form data is validated.
You made a lot of changes! Great work.
Assuming you don’t have any compiler errors, start up your application. Don’t forget to start your SQL server. Make sure you can create a new job object from the Add Jobs form, selecting a pre-existing employer.
Then, make sure the data has been saved in your job table. You should see a column for
employer_id, corresponding to the employer object selected for the new job.
You have changed the architecture of your job table. You will still be able to add a new entry that has an
employer_id column but you’ll note that job still has the now defunct employer column. You can keep your database
clean by removing the job table. It will be recreated when you run the application again.
queries.sql under “Part 3”, write the SQL statement to remove the job table.The List and Search functionality still isn’t quite fixed so to view a job in the application, make a note
of the job’s id in the SQL table. Back in your browser, enter the path for /view/{jobId}.
When everything works, move on to Part 4 below.
Using a many-to-many relationship, we can now use the Skill object to store a Job object’s skills. At the moment,
a job can have many skills listed as strings. In this section, you’ll be tasked with changing this field type to be a list
of skills. Just as a job requires many skills, any skill can be associated with several jobs. With this in mind, you’ll also
add a list of jobs as a field onto the skill class.
Warning
As before, there are a few tests in TestTaskFour that have been commented out because they depend on the code you wrote in Part 1. Open TestTaskFour in IntelliJ and find these tests. For each one:
cmd+/ on Mac or ctrl+/ on Windows.If you do not uncomment these tests, your code will not pass the autograder.
Skill.jobs¶Skill class, add a jobs field.@ManyToMany annotation
with an argument mappedBy="skills" to configure this mapping.Job.skills¶Update your Job model class to fit its many-to-many relationship with skills.
Job.skills already exists. What needs to change and/or be added to map this relationship?
Tip
Be sure to check the whole class for any necessary type updates.
HomeController, Again¶You next need to wire HomeController with the skills data in order to add skills objects to a new job.
This will look almost precisely like what you have done for employer data above. Refer back to
that section to inject the controller with skill data.
There is, however, one difference to keep in mind. The job form being processed only accepts one employer by an id
field. Many skills can be added to a single job, though. Here’s what we’ll say about how to send the right skills along with
the job form.
The code for the view has already been written. Look in templates/add.html. You’ll see a form-group section that iterates
over available skills data and renders a checkbox for each skill. Each checkbox input contains an attribute name="skills".
You’ll need to pass in that attribute value to processAddJobForm in HomeController as a @RequestParam.
@RequestParam List<Integer> skills
Then, to get the skills data from a list of ids (rather than a single id as we did with employer), use the CrudRepository method
.findAllById(ids).
List<Skill> skillObjs = (List<Skill>) skillRepository.findAllById(skills);
newJob.setSkills(skillObjs);
Note
As with a job’s employer, you only need to query your database for skills if the job model is valid.
You now have all the tools in place to re-implement the list and search views from Assignment #3: Tech Jobs (MVC Edition).
ListController class, add fields for EmployerRepository and SkillRepository, both annotated with
@Autowired.list/.
Add the right model.addAttribute(name, value) statements to pass this info into templates/list.html.Run your application and make sure you can create a new job with an employer and several skills. You should now also have restored full list and search capabilities.
queries.sql under “Part 4”, write a query to return the names of all skills that are attached to jobs in alphabetical order.
If a skill does not have a job listed, it should not be
included in the results of this query.When everything works, you’re done! Congrats!
At this point, all autograding tests should be passing. To be sure, right-click on the org.launchcode.techjobs.persistent package in src/test/java and select Run tests in… If any test fails, evaluate the failure/error message and go back to fix your code.
If a test in TestCommentedTests fails, this means that you failed to uncomment one or more tests in either TestTaskTwo or TestTaskFour. You will need to go back and uncomment the test(s) and make sure they pass.
To turn in your assignment and get credit, follow the submission instructions.