diff --git a/README.md b/README.md index 19226b0..f16b528 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Refer to the file in this project, update the following files accordingly: - [src/main/resources/mybatis/mybatis-config.xml](src/main/resources/mybatis/mybatis-config.xml) - [src/main/resources/application.properties](src/main/resources/application.properties) - [src/main/resources/mapper/mainapp.xml](src/main/resources/mapper/mainapp.xml) -- [src/main/java/com/badbuta/learnspringbootrestfulsqlite/ApiController.java](src/main/java/com/badbuta/learnspringbootrestfulsqlite/ApiController.java) +- [src/main/java/com/example/demo/ApiController.java](src/main/java/com/example/demo/ApiController.java) # Init the database and insert sample data @@ -111,6 +111,6 @@ To start this Spring Boot Application: 1. Resolve WARN "MyBatis: No MyBatis mapper was found in '[xx.mapper]' package. Please check your configuration." 1. Why `application.properties` - `spring.datasource.url` is requried even the mybatis config exist 1. what should be the correct value? `jdbc:sqlite:data` or `jdbc:sqlite:data:abc` ok ,but `jdbc:sqlite` not ok. -1. In `src/main/java/com/badbuta/learnspringbootrestfulsqlite/ApiController.java` +1. In `src/main/java/com/example/demo/ApiController.java` 1. See the TODO 1. Rewrite the controller code better \ No newline at end of file diff --git a/src/main/java/com/badbuta/learnspringbootrestfulsqlite/openapi.yml b/openapi.yml similarity index 100% rename from src/main/java/com/badbuta/learnspringbootrestfulsqlite/openapi.yml rename to openapi.yml diff --git a/pom.xml b/pom.xml index 30123af..87a45c0 100644 --- a/pom.xml +++ b/pom.xml @@ -1,15 +1,16 @@ - + 4.0.0 org.springframework.boot spring-boot-starter-parent 3.2.0 - + + - com.badbuta - learnspringbootrestfulsqlite + com.example + demo 0.0.1-SNAPSHOT Project for learning Spring Boot, RESTful API, and SQLite This is a self-learing project for Spring Boot for RESTful API with SQLite database @@ -17,20 +18,17 @@ 17 - + + org.wisdom-framework + sqlite-jdbc + 3.8.7_1 + runtime + com.google.code.gson gson 2.10.1 - - - - org.xerial - sqlite-jdbc - 3.44.1.0 - - org.springframework.boot spring-boot-starter-web @@ -40,7 +38,6 @@ mybatis-spring-boot-starter 3.0.3 - org.springframework.boot spring-boot-devtools diff --git a/src/main/java/com/badbuta/learnspringbootrestfulsqlite/ApiController.java b/src/main/java/com/badbuta/learnspringbootrestfulsqlite/ApiController.java deleted file mode 100644 index e606a00..0000000 --- a/src/main/java/com/badbuta/learnspringbootrestfulsqlite/ApiController.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.badbuta.learnspringbootrestfulsqlite; - -import org.springframework.web.bind.annotation.RestController; - -import com.badbuta.learnspringbootrestfulsqlite.entities.User; -import com.google.gson.Gson; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; - -import java.io.IOException; -import java.util.List; -import java.io.Reader; - -import org.apache.ibatis.io.Resources; -import org.apache.ibatis.session.SqlSession; -import org.apache.ibatis.session.SqlSessionFactory; -import org.apache.ibatis.session.SqlSessionFactoryBuilder; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.GetMapping; - -/** - * The RESTful API Controller Class - * The URL for this API Controller is '/api/v1' - * - * NOTE: By adding @CrossOrigin, Swagger preview can work perfectly. - * - */ -@RestController -@CrossOrigin -@RequestMapping(value = "/api/v1") -public class ApiController { - - @Value("${mybatis.config-location}") - private String myBatisConfigLocation; - - /** - * API Endpoint = /api (or /api/) - * - * @return Simple text - */ - @RequestMapping(value = { "", "/" }, method = RequestMethod.GET) - @ResponseBody - public String getRoot() { - // TODO: it is not a good practice and should return formatted JSON object - return "This is API root"; - } - - // @GetMapping("/test") - // public String getTest(@RequestParam(name = "a", required = false) String - // valA) { - // return "GET /test, param: a=" + valA; - // } - - @GetMapping("fix") - public String getUsers() { - Gson gson = new Gson(); - - String myBatisConfigPath = myBatisConfigLocation.split(":")[1]; - - try (Reader in = Resources.getResourceAsReader(myBatisConfigPath)) { - // Create SQL Session Factory from mybatis config - SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); - - // Create session - SqlSession session = factory.openSession(); - - // Select users - List result = session - .selectList("com.badbuta.learnspringbootrestfulsqlite.mapper.UserMapper.getUsers"); - - return gson.toJson(result); - - } catch (IOException e) { - // If exception, return the exception object in JSON - // TODO: it is not a good practice and should return formatted error object - System.err.println(e); - return gson.toJson(e); - } - - } -} diff --git a/src/main/java/com/badbuta/learnspringbootrestfulsqlite/UserController.java b/src/main/java/com/badbuta/learnspringbootrestfulsqlite/UserController.java deleted file mode 100644 index fe75cd3..0000000 --- a/src/main/java/com/badbuta/learnspringbootrestfulsqlite/UserController.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.badbuta.learnspringbootrestfulsqlite; - -import org.springframework.web.bind.annotation.RestController; - -import com.badbuta.learnspringbootrestfulsqlite.entities.User; -import com.badbuta.learnspringbootrestfulsqlite.services.UserService; -import org.springframework.web.bind.annotation.RequestMapping; -import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; - -/** - * The RESTful API Controller Class - * The URL for this API Controller is '/api/v1' - * - * NOTE: By adding @CrossOrigin, Swagger preview can work perfectly. - * - */ -@RestController -@CrossOrigin -@RequestMapping(value = "/api/v1") -public class UserController { - @Autowired - private UserService userService; - - /** - * Get the user(s) by id (using query-string param) - * - * @param id The ID for - * @return User object in JSON - */ - @GetMapping(value = "users", produces = "application/json") - public List getUsers() { - List users = userService.getUsers(); - return users; - - } - - @GetMapping(value = "users/{id}", produces = "application/json") - public User getUsersById(@PathVariable("id") String userId) { - User user = userService.getUserById(Integer.parseInt(userId)); - return user; - - } - - @PostMapping("users") - public User insertUser(@RequestBody User user) { - // TODO: Return always 1, need to fix - int id = userService.insertUser(user); - user.setId(id); - return user; - } - -} diff --git a/src/main/java/com/badbuta/learnspringbootrestfulsqlite/mapper/UserMapper.java b/src/main/java/com/badbuta/learnspringbootrestfulsqlite/mapper/UserMapper.java deleted file mode 100644 index ee5fa58..0000000 --- a/src/main/java/com/badbuta/learnspringbootrestfulsqlite/mapper/UserMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.badbuta.learnspringbootrestfulsqlite.mapper; - -import java.util.List; -import org.springframework.stereotype.Repository; - -import com.badbuta.learnspringbootrestfulsqlite.entities.User; - -@Repository -public interface UserMapper { - int insertUser(User user); - - User getUserById(int id); - - List getUsers(); -} diff --git a/src/main/java/com/badbuta/learnspringbootrestfulsqlite/services/UserService.java b/src/main/java/com/badbuta/learnspringbootrestfulsqlite/services/UserService.java deleted file mode 100644 index d88708a..0000000 --- a/src/main/java/com/badbuta/learnspringbootrestfulsqlite/services/UserService.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.badbuta.learnspringbootrestfulsqlite.services; - -import java.util.List; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import com.badbuta.learnspringbootrestfulsqlite.entities.User; -import com.badbuta.learnspringbootrestfulsqlite.mapper.UserMapper; - -@Service -public class UserService { - @Autowired - UserMapper userMapper; - - public int insertUser(User user) { - int insteredUserId = userMapper.insertUser(user); - return insteredUserId; - } - - public User getUserById(int id) { - return userMapper.getUserById(id); - } - - public List getUsers() { - // Map users = userMapper.getUsers(); - // return users.values().toArray(new User[0]); - return userMapper.getUsers(); - } -} diff --git a/src/main/java/com/badbuta/learnspringbootrestfulsqlite/MainApplication.java b/src/main/java/com/example/demo/MainApplication.java similarity index 74% rename from src/main/java/com/badbuta/learnspringbootrestfulsqlite/MainApplication.java rename to src/main/java/com/example/demo/MainApplication.java index 9107b70..804cd7b 100644 --- a/src/main/java/com/badbuta/learnspringbootrestfulsqlite/MainApplication.java +++ b/src/main/java/com/example/demo/MainApplication.java @@ -1,11 +1,11 @@ -package com.badbuta.learnspringbootrestfulsqlite; +package com.example.demo; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -@MapperScan("com.badbuta.learnspringbootrestfulsqlite.mapper") +@MapperScan("com.example.demo.repository") public class MainApplication { public static void main(String[] args) { diff --git a/src/main/java/com/example/demo/controller/DefaultController.java b/src/main/java/com/example/demo/controller/DefaultController.java new file mode 100644 index 0000000..96deb7a --- /dev/null +++ b/src/main/java/com/example/demo/controller/DefaultController.java @@ -0,0 +1,37 @@ +package com.example.demo.controller; + +import org.springframework.web.bind.annotation.RestController; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import org.springframework.web.bind.annotation.CrossOrigin; + +/** + * The default controller (i.e. No specific purpose) + * This controller handle root path and other testing/trying purpose path + * + * NOTE: By adding @CrossOrigin, Swagger preview can work perfectly. + * + */ +@RestController +@CrossOrigin +@RequestMapping(value = "/") +public class DefaultController { + + // @Value("${mybatis.config-location}") + // private String myBatisConfigLocation; + + /** + * Root path / + * + * @return Simple text + */ + @RequestMapping(value = "/", method = RequestMethod.GET) + @ResponseBody + public String getRoot() { + // String myBatisConfigPath = myBatisConfigLocation.split(":")[1]; + return "Hello, This is a self-learning project for spring boot for rest api using sqlite"; + } +} diff --git a/src/main/java/com/example/demo/controller/UserController.java b/src/main/java/com/example/demo/controller/UserController.java new file mode 100644 index 0000000..7a4f478 --- /dev/null +++ b/src/main/java/com/example/demo/controller/UserController.java @@ -0,0 +1,84 @@ +package com.example.demo.controller; + +import org.springframework.web.bind.annotation.RestController; + +import com.example.demo.model.User; +import com.example.demo.services.UserService; + +import org.springframework.web.bind.annotation.RequestMapping; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * The Controller Class for User API + * + * NOTE: By adding @CrossOrigin, Swagger preview can work perfectly. + * + * TODO: + * 1) How to set response content type to json by default + * 2) Handling errors + */ +@RestController +@CrossOrigin +@RequestMapping(value = "/api/v1/users") +public class UserController { + @Autowired + private UserService userService; + + /** + * Get all users + * + * Method: GET + * Endpoint: /users or /users/ + * + * @return List of User object + */ + @GetMapping(value = { "", "/" }, produces = "application/json") + public List getUsers() { + List users = userService.getUsers(); + return users; + + } + + /** + * Get the user by id + * + * Method: GET + * Endpoint: /users/{id} + * + * TODO: + * 1) Return 404 if not found + * 2) Return correct status code like 200 + * + * @param userId the userId (= path variable {id}) + * @return The User object. Empty content will be returned if not found + */ + @GetMapping(value = "{id}", produces = "application/json") + public User getUsersById(@PathVariable("id") String userId) { + User user = userService.getUserById(Integer.parseInt(userId)); + return user; + + } + + /** + * Add a user + * + * Method: POST + * Endpoint: /users + * + * @param user The User object with required fields. See swagger file + * @return The added user object + */ + @PostMapping(value = "", produces = "application/json") + public User addUser(@RequestBody User user) { + int id = userService.addUser(user); + // if success, get the user and return the created user + User addedUser = userService.getUserById(id); + return addedUser; + } +} diff --git a/src/main/java/com/badbuta/learnspringbootrestfulsqlite/entities/User.java b/src/main/java/com/example/demo/model/User.java similarity index 73% rename from src/main/java/com/badbuta/learnspringbootrestfulsqlite/entities/User.java rename to src/main/java/com/example/demo/model/User.java index cdeb965..7ab85d9 100644 --- a/src/main/java/com/badbuta/learnspringbootrestfulsqlite/entities/User.java +++ b/src/main/java/com/example/demo/model/User.java @@ -1,4 +1,4 @@ -package com.badbuta.learnspringbootrestfulsqlite.entities; +package com.example.demo.model; /** * Domain class - User @@ -9,6 +9,8 @@ public class User { private String name; private String password; + public User() { + } public int getId() { return this.id; @@ -37,10 +39,10 @@ public class User { @Override public String toString() { return "{" + - " id='" + getId() + "'" + - ", name='" + getName() + "'" + - ", password='" + getPassword() + "'" + - "}"; + " id='" + getId() + "'" + + ", name='" + getName() + "'" + + ", password='" + getPassword() + "'" + + "}"; } - + } diff --git a/src/main/java/com/example/demo/repository/UserMapper.java b/src/main/java/com/example/demo/repository/UserMapper.java new file mode 100644 index 0000000..c1e861d --- /dev/null +++ b/src/main/java/com/example/demo/repository/UserMapper.java @@ -0,0 +1,31 @@ +package com.example.demo.repository; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.example.demo.model.User; + +/** + * The Mapper class for User + * Note: + * 1) @MapperScan("") has to be added in main application source + * code. + * 2) Since MyBatis+SQLite is used, we should use MyBatis-specific approach for + * repositories. JpaRepository cannot be used. + * JpaRepository is part of Spring Data JPA, and it's designed to work with JPA + * (Java Persistence API) implementations, which include Hibernate, EclipseLink, + * and other JPA providers. It's not intended to be used with MyBatis. + * + * @see com.example.demo.MainApplication + * + */ +@Repository +public interface UserMapper { + + int addUser(User user); + + User getUserById(int id); + + List getUsers(); +} diff --git a/src/main/java/com/example/demo/repository/UserMapper.xml b/src/main/java/com/example/demo/repository/UserMapper.xml new file mode 100644 index 0000000..b7532a8 --- /dev/null +++ b/src/main/java/com/example/demo/repository/UserMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + insert into users (name, password) values (#{name}, #{password}) + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/example/demo/services/UserService.java b/src/main/java/com/example/demo/services/UserService.java new file mode 100644 index 0000000..f380a91 --- /dev/null +++ b/src/main/java/com/example/demo/services/UserService.java @@ -0,0 +1,53 @@ +package com.example.demo.services; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.example.demo.model.User; +import com.example.demo.repository.UserMapper; + +/** + * Service class for User + */ +@Service +public class UserService { + @Autowired + UserMapper userMapper; + + /** + * Add the user + * + * @param user The user object for adding + * @return Added user's id + */ + public int addUser(User user) { + // NOTE: the mapper will return the number of row inserted + // not the generated ID. + // int rowInserted = userMapper.addUser(user); + + userMapper.addUser(user); + return user.getId(); + } + + /** + * Get user by id + * + * @param id user id + * @return The user, null will be returned if not exist. + */ + public User getUserById(int id) { + return userMapper.getUserById(id); + } + + /** + * Get all user (without pagination) + * NOTE: It will cause problem if the number of record is large. + * + * @return A list of User object. The result. + */ + public List getUsers() { + return userMapper.getUsers(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5681050..b0e891f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,13 +1,23 @@ -# Setting used by @Repository +## ----- Database configuration ----- + +# Datasource URL used by @Repository, the db file location is /data/mainapp.sqlite3 spring.datasource.url=jdbc:sqlite:data/mainapp.sqlite3 + +# Datasource URL used by @Repository, the db use in-memory +# spring.datasource.url=jdbc:sqlite::memory: + spring.datasource.driver-class-name=org.sqlite.JDBC # spring.datasource.username= # spring.datasource.password= -# MyBatis related: +## MyBatis related: # mybatis.check-config-location=true -# mybatis.mapper-locations=classpath:mapper/*.xml -# mybatis.type-aliases-package=com.badbuta.learnspringbootrestfulsqlite.entities mybatis.config-location=classpath:mybatis/mybatis-config.xml +## ----- Logger configuration ----- +logging.level.org.mybatis=DEBUG +# logging.level.root=DEBUG + + +## ----- Others configuration ----- server.port=8888 diff --git a/src/main/resources/mapping/UserMapper.xml b/src/main/resources/mapping/UserMapper.xml deleted file mode 100644 index 4a939ac..0000000 --- a/src/main/resources/mapping/UserMapper.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - SELECT last_insert_rowid() - - insert into users (name,password) values (#{name},#{password}) - - - - - - \ No newline at end of file diff --git a/src/main/resources/mybatis/mybatis-config.xml b/src/main/resources/mybatis/mybatis-config.xml index 126119f..1f50f96 100644 --- a/src/main/resources/mybatis/mybatis-config.xml +++ b/src/main/resources/mybatis/mybatis-config.xml @@ -5,10 +5,12 @@ + - + + @@ -18,7 +20,10 @@ - - - + + + + \ No newline at end of file diff --git a/src/test/java/com/badbuta/learnspringbootrestfulsqlite/MainApplicationTests.java b/src/test/java/com/example/demo/MainApplicationTests.java similarity index 78% rename from src/test/java/com/badbuta/learnspringbootrestfulsqlite/MainApplicationTests.java rename to src/test/java/com/example/demo/MainApplicationTests.java index af344d3..7353ace 100644 --- a/src/test/java/com/badbuta/learnspringbootrestfulsqlite/MainApplicationTests.java +++ b/src/test/java/com/example/demo/MainApplicationTests.java @@ -1,4 +1,4 @@ -package com.badbuta.learnspringbootrestfulsqlite; +package com.example.demo; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest;