- SpringBoot
- Spring Data JPA
- Lombok
- Mapstruct
- H2 in-memory DB
- PostgreSQL
- Docker
- Docker compose
- Flyway
- Application support H2 in-mem DB and Postgres DB, by default H2 in-mem DB is being used.
- DB data is pre-populated by Flyway
There are mutiple ways to run application
the default application.yaml will run profile h2, you can change it
- Run the command to build the whole project: gradle clean build
- start the application with H2 in-mem DB: java -jar ./build/libs/todos-0.0.1-SNAPSHOT.jar --spring.profiles.active=h2
- Run the command to build the whole project: gradle clean build
- with docker-compose.yaml, so you can only docker-compose up todos-postgres
- start the application with Postgres DB: java -jar ./build/libs/todos-0.0.1-SNAPSHOT.jar --spring.profiles.active=postgres
- Run the command to build the whole project: gradle clean build
- run docker-compose up, then application and postgres DB are both up and running.
if you start the application successful, the Swagger API is here: http://localhost:8080/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config
In here, I'll use Microsoft todos' UI for example
below data is pre-populated:
insert into users (create_time, encoded_password, user_name, id)
values ('2021-06-22T13:57:34.920Z', '$2a$10$L5rUMjVqkojrPaO80SC.VOPxPRd7eoNrkWBrFo3lDy1lDocc8RMmK', 'tim', 1);
insert into users (create_time, encoded_password, user_name, id)
values ('2021-06-22T13:57:34.920Z', '$2a$10$L5rUMjVqkojrPaO80SC.VOPxPRd7eoNrkWBrFo3lDy1lDocc8RMmK', 'jean', 2);
insert into ilist (create_time, due_date, name, update_time, user_id, id)
values ('2021-06-22T13:57:34.920Z', '2021-06-22T13:57:34.920Z', 'my tasks', '2021-06-22T13:57:34.920Z', 1, 1);
insert into item (create_time, deadline, description, ilist_id, order_num, update_time, id)
values ('2021-06-22T13:57:34.920Z', '2021-06-22T13:57:34.920Z', 'item1 desc', 1, 0, '2021-06-22T13:57:34.920Z', 1);
insert into item (create_time, deadline, description, ilist_id, order_num, update_time, id)
values ('2021-06-22T13:57:34.920Z', '2021-06-22T13:57:34.920Z', 'item2 desc', 1, 0, '2021-06-22T13:57:34.920Z', 2);
insert into item (create_time, deadline, description, ilist_id, order_num, update_time, id)
values ('2021-06-22T13:57:34.920Z', '2021-06-22T13:57:34.920Z', 'item3 desc', 1, 0, '2021-06-22T13:57:34.920Z', 3);
use user's id to find lists first
when click particular list, it will bring items by list id
enter new list name, then press enter, will call this API to create a new list
like create list's UI
in items, we can move item, the new order should be stored
in here, frontend directly send the new data array for backend to save
after save, you can query items again to check it out.
I think time complexity in this project is about the POSTGRES DB operations' time complexity and my program's time complexity
in POSTGRES DB, gerenarally with the binary block size.
- If no index, search is O(n).
- If index, search is O(log n).
You can also set which datastructure you want to use in which index. For instance, B-tree as the method of the partial index here and about the complexities of different operations here for the binary operations:
find user and related list are all used pk and fk, so the time complexity is O(logn).
but in programming side, we need to transform all data to another dto, so finally the time complexity is still O(n).
public List<IListGetDto> findAllListByUserId(Long userId) {
return this.getUser(userId).getLists().stream()
.map(iList -> iListMapper.fromEntity(iList))
.collect(Collectors.toList());
}
private User getUser(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new DataNotExistException("cant find user"));
return user;
}
and other APIs have the same situation, so I think all the other APIs are all O(n).
The only exception is for remove, the time complexity are all O(logn).
public List<ItemGetDto> findItemsByIListId(Long listId) {
return iListRepository.findItemsByListId(listId)
.orElseThrow(() -> new DataNotExistException("cant find list"))
.getItems().stream()
.map(item -> itemMapper.fromEntity(item))
.collect(Collectors.toList());
}
@Transactional(rollbackFor = Exception.class)
public IListGetDto createIList(IListPostDto dto, Long userId) {
User user = getUser(userId);
IList iList = iListMapper.toEntity(dto);
iList.setUser(user);
return iListMapper.fromEntity(iListRepository.save(iList));
}
@Transactional(rollbackFor = Exception.class)
public IListGetDto saveIList(IListPostDto dto, Long listId) {
IList iList = getiList(listId);
iListMapper.updateIList(dto, iList);
return iListMapper.fromEntity(iListRepository.save(iList));
}
@Transactional(rollbackFor = Exception.class)
public ItemGetDto createItem(ItemPostDto dto, Long listId) {
IList iList = getiList(listId);
Item item = itemMapper.toEntity(dto);
item.setIlist(iList);
return itemMapper.fromEntity(itemRepository.save(item));
}
@Transactional(rollbackFor = Exception.class)
public ItemGetDto saveItem(ItemPutDto dto, Long itemId) {
Item item = getItem(itemId);
itemMapper.updateItem(dto, item);
return itemMapper.fromEntity(itemRepository.save(item));
}
@Transactional(rollbackFor = Exception.class)
public void removeList(Long listId) {
iListRepository.delete(this.getiList(listId));
}
@Transactional(rollbackFor = Exception.class)
public void removeItem(Long itemId) {
itemRepository.delete(this.getItem(itemId));
}
private User getUser(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new DataNotExistException("cant find user"));
return user;
}
private IList getiList(Long listId) {
return iListRepository.findById(listId)
.orElseThrow(() -> new DataNotExistException("cant find list"));
}
private Item getItem(Long itemId) {
return itemRepository.findById(itemId)
.orElseThrow(() -> new DataNotExistException("cant find item"));
}
this one has a for loop to iterate all items and update the DB
so the time complexity is O(m*logn), m is item size
@Transactional(rollbackFor = Exception.class)
public void saveMovedItemsOrder(List<ItemPostDto> items) {
for (int i = 0; i < items.size(); i++) {
ItemPostDto dto = items.get(i);
Item it = getItem(dto.getId());
it.setOrderNum(i);
itemRepository.save(it);
}
}