3

I'm building an API in Spring Boot with the goal of returning a list of daily usage data and monthly usage data from an Oracle database in a single response (a requirement). I've managed to get the below working with both monthly and daily endpoints okay but I'm struggling to get them to return in a single response (as shown in "output" below).

From my understanding the best way of doing this is to create a service class (AllData) which will contain both daily and monthly lists and then return that in the controller request (DataController) although I'm not sure on what the structure of that actually looks like in AllData & DataController? Let me know if I need to elaborate on this further.

I have changed some of the names around here for the example.

MonthlyData.java

@Entity
@Table(schema = "TEST", name = "MONTHLYDATA")
public class MonthlyData {

    @Id
    private long id;
    private String username;
    private String month;
    private Integer usage;

    public MonthlyData(){
    }

    public String getUserName(){
        return username;
    }

    public String getMonth(){
        return month;
    }

    public Integer getUsage(){
        return usage;
    }
}

DailyData.java

@Entity
@Table(schema = "TEST", name = "DAILYDATA")
public class DailyData {

    @Id
    private long id;
    private String username;
    private String date;
    private Integer usage;

    public DailyData(){
    }

    public String getUserName(){
        return username;
    }

    public String getDate(){
        return date;
    }

    public Integer getUsage(){
        return usage;
    }
}

MonthlyDataRepository.java

// Populates a list of MonthlyData from the Oracle database
public interface MonthlyDataRepository extends PagingAndSortingRepository<MonthlyData, String> {
    List<MonthlyData> findByUserName(String username);
}

DailyDataRepository.java

// Populates a list of DailyData from the Oracle database
public interface DailyDataRepository extends PagingAndSortingRepository<DailyData, String> {
    List<DailyData> findByUserName(String username);
}

AllData.java

@Service("AllData")
public class AllData {

    @Autowired
    DailyDataRepository dailyDataRepository;

    @Autowired
    MonthlyDataRepository monthlyDataRepository;

    // Something to combine and return both Daily and Monthly data lists

    public List <DailyData> readDailyData(String username) {  
        return dailyDataRepository.findByUserName(username);
    }

    public List <MonthlyData> readMonthlyData(String username) {  
        return monthlyDataRepository.findByUserUserName(username);
    }
}

DataController.java

@RestController()
@RequestMapping("/data")
public class DataController {

    @RequestMapping(path = "{username}", method = RequestMethod.GET)
    public List <AllData> readAllData(@PathVariable String username) {  
        // Return the AllData list here???
    }
}

Output:

http://localhost:8080/data/dave


<List>
    <item>
        <date>11122017</date>
        <usage>24562</usage>
    </item>
    <item>
        <date>10122017</date>
        <usage>123546</usage>
    </item>
</List>
<List>
    <item>
        <month>November</month>
        <usage>6745234634</usage>
    </item>
    <item>
        <month>December</month>
        <usage>242346</usage>
    </item>
</List>
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Matt Damon
  • 323
  • 1
  • 6
  • 18
  • what are you asking? Asking if your attempt is valid? Or do you have problems with your attempt? – Yannic Bürgmann Dec 11 '17 at 10:29
  • A bit of both. My attempt isn't complete above, I'm struggling to setup the AllData class to contain both the repositories and the controller which returns it. – Matt Damon Dec 11 '17 at 10:38

1 Answers1

2

You can do

@RequestMapping(path = "{username}", method = RequestMethod.GET)
public Map readAllData(@PathVariable String username) {  
    Map<String, Object> items = new HashMap();
    items.put("dailyData", allData.readDailyData(username));
    items.put("monthlyData", allData.readMonthlyData(username));
    return items;
}

Or you can create an object to contain both instead of using a map. Keep in mind this approach will require two queries on the database.

prettyvoid
  • 3,446
  • 6
  • 36
  • 60
  • Thanks for the reply! That looks ideal but I'm getting "Cannot make a static reference to the non-static method readDailyData from the type AllData" errors on that. – Matt Damon Dec 11 '17 at 10:45
  • @MattDamon Make sure that you autowire the `AllData` bean into your controller and that you're using a reference to that bean. According to that error it looks like you replaced the code from @prettyvoid by `AllData.readDailyData(username)` in stead of `allData.readDailyData(username)`. – g00glen00b Dec 11 '17 at 11:39
  • Ah of course! Yeah looks to be working. In this example is there any advantage to having the AllData class at all when I could just use the repository in the controller directly `items.put("dailyData", dailyDataRepository.findByUserName(username));` ? – Matt Damon Dec 11 '17 at 11:49
  • @MattDamon If there is no business logic involved then I guess it's fine to use the dao (aka repository) from the controller layer. Read this https://stackoverflow.com/a/6130807/2058534. Also when you create services it's a good convention to include `Service` in the class name. For example: UserService. – prettyvoid Dec 11 '17 at 12:06
  • Thanks, I've had a read and it makes sense to keep it in a service, at the moment I'm only getting data but will be useful if I ever need to validate a post/put request. Clears up the controller a bit too. – Matt Damon Dec 11 '17 at 13:24
  • I ended up creating a master `` map so I could put the daily and monthly maps within that to split out the output tags rather than them all being merged. Would it be possible to rename the `` tags from the output? – Matt Damon Dec 11 '17 at 13:25
  • What tag are you referring to? Is your controller returning XML rather than JSON? – prettyvoid Dec 11 '17 at 13:34
  • Yes returning XML sadly. – Matt Damon Dec 11 '17 at 15:44
  • Is XML a requirement? @RestController by default returns json. At the end I think the tag names are equal to the keys in Map. – prettyvoid Dec 11 '17 at 15:54
  • Unfortunately it is, I did push for json. The tag names within the map are fine but ideally I'd like to rename the actual `` tag to something else if at all possible. The rest of the XML structure is fine. – Matt Damon Dec 11 '17 at 16:09
  • I think to achieve that you'll have to stop using a Map and create your own object(s) with the structure that you like, execute both queries in your service layer, populate your object(s) and return them. I think that way spring won't use `` or `` and will use your object names or names defined by annotations above fields. This is a similar question: https://stackoverflow.com/questions/16657281/cant-rename-xml-json-root-element-in-spring-3-2-2-jackson-2-2-and-xstream Good luck – prettyvoid Dec 11 '17 at 16:35