I have the below repository which extends JpaRepository.
public interface UserRepository extends JpaRepository<User, Long> {
// #Query(value = "SELECT * FROM users u WHERE u.email = ?1", nativeQuery = true)
User findByEmail(String email);
}
When I call the function from the below mapping no result is returned, no empty object, nothing.
#GetMapping(value = "email")
public User getByEmail(#RequestBody String email) {
return userRepository.findByEmail(email);
}
Found similar issues but with no actual answers. I have also tried native queries, as you can see the commented #Query annotation. The mapping is in a simple controller which just has a post function and a get function for all the users.
What am I doing wrong?
Edit ---
User model
package com.example.demo.User;
import javax.persistence.*;
#Entity(name="Users")
#Table(name = "users", uniqueConstraints = {
#UniqueConstraint(name = "user_email_unique", columnNames = "email")
})
public class User {
#Id
#SequenceGenerator(
name = "users_sequence",
sequenceName = "users_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "users_sequence"
)
#Column(name = "id", updatable = false) // Column options for the id
private long id;
#Column(name = "first_name", nullable = false, columnDefinition = "TEXT")
private String name;
private String lastName;
#Column(nullable = false, columnDefinition = "TEXT")
private String email;
private int age;
private int weight;
public User() {}
public User(String name, String lastName, String email, int age, int weight) {
this.name = name;
this.lastName = lastName;
this.email = email;
this.age = age;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
I have found the answer in the docs of spring boot, only POST/PUT requests have a request body.
#RequestBody annotation binds the content sent in (POST / PUT) request body with the annotated variable. Since there is no 'body' part in GET request, spring throws HttpMessageNotReadableException to indicate the same.
As a general rule, you can only use #RequestBody for the requests which can have 'body' content e.g. POST or PUT.
Related
I try to get the return value using JPA.
But my database Procedure returns multiple results, and JPA only gets the first return value. as the picture shows.
The result I want is the third one, but I only get the first one.
How to get the third one? Thanks.
Java Result
Database Result
Entity.java
#Entity
#NamedStoredProcedureQueries(
{
#NamedStoredProcedureQuery(
name = "SpringTestProcedure",
procedureName = "SpringTestProcedure",
parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "id", type = Test.class),
#StoredProcedureParameter(mode = ParameterMode.IN, name = "firstName", type = Test.class),
#StoredProcedureParameter(mode = ParameterMode.IN, name = "lastName", type = Test.class)
}
)
}
)
#Table(name = "Users")
public class Test {
public Test() {
}
public Test(Integer id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
Repository.java
#Repository
public interface SpringTestRepository extends JpaRepository<Test, Integer> {
#Procedure(procedureName = "SpringTestProcedure")
public List<Object[]> getEntity(#Param("id") Integer id,
#Param("firstName") String firstName,
#Param("lastName") String lastName);
}
ServiceImpl.java
#Service
public class SpringTestServiceImpl implements SpringTestService {
#Autowired
private SpringTestRepository springTestRepository;
#Override
public Test getEntity(Integer id, String firstName, String lastName) {
List<Object[]> list = springTestRepository.getEntity(id, firstName, lastName);
for (Object[] object : list) {
System.out.println(Arrays.toString(object));
}
return null;//springTestRepository.getEntity(id, firstName, lastName);
}
}
Change the type of parameters
parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "id", type = Integer.class),
#StoredProcedureParameter(mode = ParameterMode.IN, name = "firstName", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.IN, name = "lastName", type = String.class)
}
Change method getEntity
#Override
public Test getEntity(Integer id, String firstName, String lastName) {
List<Test> list = springTestRepository.getEntity(id, firstName, lastName);
for (Test test : list) {
System.out.println(test.getId()+" "+test.getFirstName()+" "+test.getLastName());
}
return list.get(2);//springTestRepository.getEntity(id, firstName, lastName);
}
I tried to create a query that returns different columns from two tables, and I want the query columns to be mapped to the user definition class.
my Student Model :
package com.example.demo.models;
import javax.persistence.*;
import java.util.List;
#Entity
#Table
public class Students {
public Students() {
}
public Students(String firstName, String lastName, String age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
#Column
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column
private String firstName;
#Column
private String lastName;
#Column
private String age;
#ManyToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "Student_Course",
joinColumns = #JoinColumn(name="studentID"),
inverseJoinColumns = #JoinColumn(name="courseID"))
private List<Course> courses;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public List<Course> getCourses() {
return courses;
}
public void setCourses(List<Course> courses) {
this.courses = courses;
}
}
my Course Model :
package com.example.demo.models;
import javax.persistence.*;
import java.util.List;
#Entity
#Table
public class Course {
public Course() {
}
public Course(String courseName, String unitCount) {
this.courseName = courseName;
this.unitCount = unitCount;
}
#Column
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "CourseName")
private String courseName;
#Column
private String unitCount;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "Student_Course",
joinColumns = #JoinColumn(name="courseID"),
inverseJoinColumns = #JoinColumn(name="studentID"))
private List<Students> students;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "teacherID")
private Teachers teachers;
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getUnitCount() {
return unitCount;
}
public void setUnitCount(String unitCount) {
this.unitCount = unitCount;
}
public List<Students> getStudents() {
return students;
}
public void setStudents(List<Students> students) {
this.students = students;
}
public Teachers getTeachers() {
return teachers;
}
public void setTeachers(Teachers teachers) {
this.teachers = teachers;
}
}
my Query in Service Layer:
#Transactional
public List<StudentInfo> getStudentInfo(){
Session session = sf.openSession();
Query hql = session.createQuery("select std.firstName, std.lastName, c.courseName from Students std join std.courses c");
var data = hql.list();
session.close();
return data;
}
and i want map query columns to this simple class :
package com.example.demo.ViewModels;
public class StudentInfo {
private String firstName;
private String lastName;
private String courseName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
}
and in finally..
my controller Class :
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView Index() {
List<StudentInfo> data = studentRepository.getAll();
return new ModelAndView("indexView", "data", data);
}
notice : i`m using thymeleaf in this project.
please help me.
thanks.:D
If you use Spring Data JPA you should be able to do it in the repository using the #Query annotation:
#Query(value = "SELECT new com.path.to.StudentInfo(std.firstName, " +
"std.lastName, c.courseName) " +
"FROM Students std join std.courses c"
List<StudentInfo> getAllStudentInfo();
Make sure you have an all-args constructor in StudentInfo though.
If you use Hibernate, it's almost the same:
entityManager.createQuery("SELECT new com.path.to.StudentInfo(std.firstName, " +
"std.lastName, c.courseName) " +
"FROM Students std join std.courses c",
StudentInfo.class)
Edit: I have concerns about whether it's supposed to work when using join, but give it a try regardless.
I have the next situation. I ahve entity object User:
package models;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
import org.hibernate.annotations.Proxy;
#Entity
#Table(name="users")
#Proxy(lazy=true)
public class User {
private int id;
private String login;
private String password;
private String name;
private String email;
private Integer age;
private String country;
private Set<UserRole> roles = new HashSet<UserRole>();
private UserStatus status;
private Date created;
private Date updated;
public User() {
status=UserStatus.A;
}
public User(String user_login, String user_password, String user_name, String user_email) {
this.login = user_login;
this.password = user_password;
this.name = user_name;
this.email = user_email;
status=UserStatus.A;
}
public User(String user_login, String user_password, String user_name, String user_email, int age) {
this(user_login, user_password, user_name, user_email);
this.age = age;
}
public User(String user_login, String user_password, String user_name, String user_email, int age, String country) {
this(user_login, user_password, user_name, user_email, age);
this.country = country;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="user_id", unique = true)
public int getId() {
return id;
}
public void setId(int user_id) {
this.id = user_id;
}
#Column(name="user_login")
public String getLogin() {
return login;
}
public void setLogin(String user_login) {
this.login = user_login;
}
#Column(name="user_password")
public String getPassword() {
return password;
}
public void setPassword(String user_password) {
this.password = user_password;
}
#Column(name="user_name")
public String getName() {
return name;
}
public void setName(String user_name) {
this.name = user_name;
}
#Column(name="user_email")
public String getEmail() {
return email;
}
public void setEmail(String user_email) {
this.email = user_email;
}
#Column(name="user_age")
public Integer getAge() {
return age;
}
public void setAge(Integer user_age) {
this.age = user_age;
}
#Column(name="user_country")
public String getCountry() {
return country;
}
public void setCountry(String user_country) {
this.country = user_country;
}
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "users_to_userroles", joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "user_role_id ") })
public Set<UserRole> getRoles() {
return roles;
}
public void setRoles(Set<UserRole> user_roles) {
this.roles = user_roles;
}
#Column(name="user_status")
#Enumerated(EnumType.STRING)
public UserStatus getStatus() {
return status;
}
public void setStatus(UserStatus status) {
this.status = status;
}
#Transient
#Column(name="user_created")
public Date getCreated() {
return created;
}
public void setCreated(Date user_created) {
this.created = user_created;
}
#Transient
#Column(name="user_updated")
public Date getUpdated() {
return updated;
}
public void setUpdated(Date user_updated) {
this.updated = user_updated;
}
}
And JSP page (simple form, not related to question) with the form to create new user and table to show all existing users. I have used binding between form and Entity object User (it is inside controller):
User user = new User();
List<User> users = userService.getAllUsers();//to fill table with users
List<UserRole> userRoles = userRolesService.getAllRoles();//to fill tables with users
model.addAttribute("rolesList", userRoles);
model.addAttribute("users", users);
model.put("adminForm", user);//Here adminForm is the name of form in JSP page
Now what is the problem: as you see User has two fields user_created and user_updated (they are created automatically by Postgres server). They are forwarded withh all other fields to table in JSP page. BUT my form in JSP does not provide these fields (no need - right)))), so they are null when transfered from form to controller. And now Hibernate can not add line on Postgres server because two fields are empty((( So my question is:
can I somehow mark these columns as #Transient but only when I save entity not read it from database.
I know I still can bind separate field in form not the whole object. But still is it possible to do what I ask? With existing configuration, new User is saved but these two fields are not read and JSP table columns are empty(((
You need to set the insertable and updatable properties of your column mapping to false. This will make the field read-only for Hibernate.
#Column(name="user_created", insertable=false, updatable=false)
Hi I can't get merge working it only makes new record but don't update record
EDIT:
Object:
#Entity(name = "ALLEGRO_TRANSACTION")
public class AllegroTransactionImpl implements AllegroTransaction{
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
#Column(name = "ALIEXPRESS_NUMBER")
protected String aliexpressNumber;
#Column(name = "CREATE_DATE")
protected Date createDate;
#OneToOne(optional = true, targetEntity = PaymentTypeImpl.class)
#JoinColumn(name = "PAYMENT_ID")
protected PaymentTypeImpl paymentType;
#Column(name = "FIRST_NAME")
protected String firstName;
#Column(name = "LAST_NAME")
protected String lastName;
#Column(name = "PRICE")
protected float price;
#Column(name = "EMAIL")
protected String email;
#Column(name = "PHONE", nullable = true)
protected String phone;
#Column(name = "ADDRESS", columnDefinition="LONGTEXT")
protected String address;
#Column(name = "ATTENTION", columnDefinition="LONGTEXT")
protected String attention;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAliexpressNumber() {
return aliexpressNumber;
}
public void setAliexpressNumber(String aliexpressNumber){
this.aliexpressNumber = aliexpressNumber;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public PaymentTypeImpl getPaymentType() {
return paymentType;
}
public void setPaymentType(PaymentTypeImpl paymentType) {
this.paymentType = paymentType;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getAttention() {
return attention;
}
public void setAttention(String attention) {
this.attention = attention;
}
}
Manager:
#Service
public class AllegroTransactionService {
private final static Logger logger = Logger.getLogger(AllegroTransactionService.class);
#PersistenceContext( unitName = "allegroTransactionPersistenceUnit", type= PersistenceContextType.EXTENDED )
protected EntityManager em;
public List<AllegroTransactionImpl> readAllegroTransactionByCreateDate()
{
Query query = this.em.createQuery( "SELECT allegroTransaction FROM com.springapp.mvc.classes.AllegroTransactionImpl allegroTransaction ORDER BY createDate DESC" );
return query.getResultList();
}
#Transactional
public AllegroTransactionImpl saveAllegroTransaction(AllegroTransactionImpl allegroTransaction)
{
this.em.merge( allegroTransaction );
return allegroTransaction;
}
}
Still can't get this working ... Maybe you guys figure something out ? Entity manager works i can easly get readAllegroTransaction function working but merge don't work - its create another object.
Problem was not passing correctly ID parameter
I'm new to hibernate and web services and creating a project for tailor system in dropwizard.
When i try to run the project through cmd as in DropWizard Sites gets:
INFO [2014-01-18 08:41:13,784] org.hibernate.annotations.common.Version: HCANN0
00001: Hibernate Commons Annotations {4.0.1.Final}
INFO [2014-01-18 08:41:13,828] org.hibernate.Version: HHH000412: Hibernate Core
{4.1.9.Final}
INFO [2014-01-18 08:41:13,847] org.hibernate.cfg.Environment: HHH000206: hibern
ate.properties not found
INFO [2014-01-18 08:41:13,850] org.hibernate.cfg.Environment: HHH000021: Byteco
de provider name : javassist
INFO [2014-01-18 08:41:14,076] com.yammer.dropwizard.hibernate.SessionFactoryFa
ctory: Entity classes: [com.yammer.dropwizard.tailor.model.CoatModel, com.yammer
.dropwizard.tailor.model.CustomerModel, com.yammer.dropwizard.tailor.model.LongS
hirtModel, com.yammer.dropwizard.tailor.model.OrderModel, com.yammer.dropwizard.
tailor.model.ShirtModel, com.yammer.dropwizard.tailor.model.TailorModel, com.yam
mer.dropwizard.tailor.model.TrouserModel]
Exception in thread "main" org.hibernate.MappingException: Could not determine t
ype for: com.yammer.dropwizard.tailor.model.CustomerModel, at table: Order, for
columns: [org.hibernate.mapping.Column(customer)]
at org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:314)
at org.hibernate.mapping.SimpleValue.isValid(SimpleValue.java:292)
at org.hibernate.mapping.Property.isValid(Property.java:239)
at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:4
69)
at org.hibernate.mapping.RootClass.validate(RootClass.java:270)
at org.hibernate.cfg.Configuration.validate(Configuration.java:1294)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.jav
a:1742)
at com.yammer.dropwizard.hibernate.SessionFactoryFactory.buildSessionFac
tory(SessionFactoryFactory.java:77)
at com.yammer.dropwizard.hibernate.SessionFactoryFactory.build(SessionFa
ctoryFactory.java:35)
at com.yammer.dropwizard.hibernate.HibernateBundle.run(HibernateBundle.j
ava:38)
at com.yammer.dropwizard.hibernate.HibernateBundle.run(HibernateBundle.j
ava:13)
at com.yammer.dropwizard.config.Bootstrap.runWithBundles(Bootstrap.java:
64)
at com.yammer.dropwizard.cli.EnvironmentCommand.run(EnvironmentCommand.j
ava:37)
at com.yammer.dropwizard.cli.ConfiguredCommand.run(ConfiguredCommand.jav
a:58)
at com.yammer.dropwizard.cli.Cli.run(Cli.java:53)
at com.yammer.dropwizard.Service.run(Service.java:61)
at com.yammer.dropwizard.tailor.service.TailorService.main(TailorService
.java:25)
Classes:
CustomerModel class:
#NamedQueries({
#NamedQuery(
name = "com.yammer.dropwizard.tailor.model.CustomerModel.findAll",
query = "SELECT c FROM CustomerModel c"
),
#NamedQuery(
name = "com.yammer.dropwizard.tailor.model.CustomerModel.findById",
query = "SELECT c FROM CustomerModel c WHERE c.ID = :ID"
)
})
#Entity
#Table(name = "Customer")
public class CustomerModel {
#Id
#GeneratedValue
#Column(name = "c_id")
int ID;
#Column(name = "c_code")
String customerCode;
#Column(name = "c_fname")
String firstName;
#Column(name = "c_mname")
String middleName;
#Column(name = "c_lname")
String lastName;
#Column(name = "c_nic")
String NIC_Number;
#Column(name = "c_email")
String email;
#Column(name = "c_pnumber")
String number;
public int getID() {
return ID;
}
public void setID(int ID) {
this.ID = ID;
}
public String getCustomerCode() {
return customerCode;
}
public void setCustomerCode(String customerCode) {
this.customerCode = customerCode;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getNIC_Number() {
return NIC_Number;
}
public void setNIC_Number(String NIC_Number) {
this.NIC_Number = NIC_Number;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}}
which other class should i list??
please help me.
More class:
Database Configuration class:
public class databaseConfiguration extends Configuration {
#Valid
#NotNull
#JsonProperty
DatabaseConfiguration dbconfigurations = new DatabaseConfiguration();
public DatabaseConfiguration getDatabaseConfiguration() {
return dbconfigurations;
}
}
.YML file
dbconfigurations:
# the name of your JDBC driver
driverClass: org.sqlite.JDBC
# the username
user:
# the password
password:
url: jdbc:sqlite:TailorDB.db
Service Class:
public class TailorService extends Service<databaseConfiguration> {
public static void main(String[] args) throws Exception {
new TailorService().run(args);
}
private final HibernateBundle<databaseConfiguration> hibernate = new HibernateBundle<databaseConfiguration>(CustomerModel.class,OrderModel.class,CoatModel.class,LongShirtModel.class,ShirtModel.class,TailorModel.class,TrouserModel.class) {
#Override
public DatabaseConfiguration getDatabaseConfiguration(databaseConfiguration configuration) {
return configuration.getDatabaseConfiguration();
}
};
#Override
public void initialize(Bootstrap<databaseConfiguration> bootstrap) {
// TODO Auto-generated method stub
bootstrap.setName("tailor");
bootstrap.addBundle(hibernate);
}
#Override
public void run(databaseConfiguration configuration, Environment environment)
throws Exception {
// TODO Auto-generated method stub
final CustomerDAO cdao = new CustomerDAO(hibernate.getSessionFactory());
final OrderDAO odao = new OrderDAO(hibernate.getSessionFactory());
environment.addResource(new TailorResource(cdao,odao));
}
}
After the first glance it seems that your sessionFactory don't know about the CustomerModel entity. Make sure it is added into the sessionFactory as a mapping file.
From the other proposed answer: "Make sure it is added into the sessionFactory as a mapping file."
This is probably exactly what Dropwizard is trying to avoid. However, they did a really bad job at their tutorial page here http://dropwizard.codahale.com/manual/hibernate/
Basically, follow that page, this error is guaranteed what you will get. Because it did not cover a very important part. Someone was too careless or too lazy to copy paste the "Person" class they use in the tutorial. Here is it from https://github.com/dropwizard/dropwizard/blob/master/dropwizard-example/src/main/java/com/example/helloworld/core/Person.java
package com.example.helloworld.core;
import javax.persistence.*;
#Entity
#Table(name = "people")
#NamedQueries({
#NamedQuery(
name = "com.example.helloworld.core.Person.findAll",
query = "SELECT p FROM Person p"
),
#NamedQuery(
name = "com.example.helloworld.core.Person.findById",
query = "SELECT p FROM Person p WHERE p.id = :id"
)
})
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "fullName", nullable = false)
private String fullName;
#Column(name = "jobTitle", nullable = false)
private String jobTitle;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getJobTitle() {
return jobTitle;
}
public void setJobTitle(String jobTitle) {
this.jobTitle = jobTitle;
}
}
Now you can see that at the top, there is the annotation specify that this class is an entity and the table name. I spent 2 hours try to make sense of this problem. What were they thinking leaving this out of the tutorial!