2

I'm learning Spring with "Spring in action 5" and faced some problem: when i switch to Spring Data JPA from JDBC (which is 100% working correctly) in chapter 3, the code stops working as i try to open main page with taco's ingredients. I made a few logs to see what's going on and found out that method findById(String id) can't convert my value from DB (or something like that). I'm using MySQL.

I tried to call convertor's method convert(String id) by myself using @Autowired, but the only thing i discovered was that when key is wrong, another error will appear. So data is visible. I will try to provide some code here, but i'm not sure what is useful and what is not. I'm getting error on the first attemp to log something. Errors in IDE and in browser are different. Here's full project https://github.com/thedistantblue/taco-cloud-jpa.

Here's my converter:

    public class IngredientByIdConverter implements Converter<String, 
    Ingredient> {

    private IngredientRepository ingredientRepo;

    @Autowired
    public IngredientByIdConverter(IngredientRepository ingredientRepo) {
        this.ingredientRepo = ingredientRepo;

    }
    @Override
        public Ingredient convert(String id) {
            log.info("In converter.convert(): " 
        +ingredientRepo.findById(id).toString());
            Optional<Ingredient> optionalIngredient = 
        ingredientRepo.findById(id);
            return optionalIngredient.orElse(null);
        }
    }

And controller class:

    @Slf4j
    @Controller
    @RequestMapping("/design")
    @SessionAttributes("order")
    public class DesignTacoController {

    @ModelAttribute(name = "order")
    public Order order() {
        return new Order();
    }

    @ModelAttribute(name = "taco")
    public Taco taco() {
        return new Taco();
    }

    private final IngredientRepository ingredientRepository;
    private TacoRepository designRepository;
    private IngredientByIdConverter converter;

    @Autowired
    public DesignTacoController(IngredientRepository ingredientRepository,
                                TacoRepository designRepository,
                                IngredientByIdConverter converter) {
        this.ingredientRepository = ingredientRepository;
        this.designRepository = designRepository;
        this.converter = converter;
    }

    @GetMapping
    public String showDesignForm(Model model) {
        List<Ingredient> ingredients = new ArrayList<>();



        log.info(converter.convert("CARN").getName());
        log.info("in DTC: " + ingredientRepository.findAll());

        ingredientRepository.findAll().forEach(i -> ingredients.add(i));

In IDE:

java.lang.NumberFormatException: For input string: "PROTEIN" at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2054) ~[na:na] at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110) ~[na:na] at java.base/java.lang.Double.parseDouble(Double.java:543) ~[na:na]

In browser:

There was an unexpected error (type=Internal Server Error, status=500). For input string: "PROTEIN"; nested exception is java.lang.NumberFormatException: For input string: "PROTEIN" org.springframework.dao.InvalidDataAccessApiUsageException: For input string: "PROTEIN"; nested exception is java.lang.NumberFormatException: For input string: "PROTEIN" at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:373) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255) at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527) at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)

DistantBlue
  • 1,424
  • 1
  • 9
  • 14
  • Based on your entity definition, may be missing @Enumerated annotation: https://stackoverflow.com/a/21073312/2958086 – Compass Jul 08 '19 at 20:36
  • @Compass, thank you very much! Working well now. But may i ask about the nature of this exception and what's going on behind the scenes? – DistantBlue Jul 08 '19 at 20:45
  • Typically, you store ENUM by a mapping value to conserve space on the DB, i.e. 0,1,2,3,4. This allows you to modify the actual ENUM display value without breaking your DB. If it's just for practice, you can use the String value, but if PROTEIN suddenly became PROTEINS on the front end, you'd have to map PROTEIN -> PROTEINS on the back end, which is relatively messy. @Enumerated basically takes the String values at face value. – Compass Jul 08 '19 at 20:53

2 Answers2

10

For Ingredient.java need to add annotation @Enumerated(EnumType.STRING) for field Type type

@Id
@NaturalId(mutable = false)
private final String id;
private final String name;
@Enumerated(EnumType.STRING)
private final Type type;

public static enum Type {
    WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
}
Pavel
  • 116
  • 2
  • 4
0

@Pavel 's solution does work.

I just want to point out that there is another thing, may be worth checking.

This book talks about JDBC before JPA, and it uses data.sql to insert data into table 'Ingredient'. In file data.sql, the type of 'Ingredient.Type', whose value contains 'WRAP', 'PROTEIN', etc. , is String.

However, in the example of JPA, the content of data.sql is moved to TacoCloudApplication, which is wroted in method 'dataLoader', and, in this method, you can see that it just creates Ingredient instance with Ingredient.Type(not a String).

To find the difference between two ways, you can run TacoCloudApplication, then look up Table 'Ingredient'.

The value of Type field is String, if you use the JDBC version example code. The value of Type field is Interger, if you use the JPA version example code.

frankcrc
  • 31
  • 1
  • 4