This commit is contained in:
BadBUTA 2023-12-21 19:05:45 +08:00
parent a0ff867b66
commit acca73a548
18 changed files with 280 additions and 250 deletions

View File

@ -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

27
pom.xml
View File

@ -1,15 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/> <!-- lookup parent from repository -->
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<groupId>com.badbuta</groupId>
<artifactId>learnspringbootrestfulsqlite</artifactId>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Project for learning Spring Boot, RESTful API, and SQLite</name>
<description>This is a self-learing project for Spring Boot for RESTful API with SQLite database</description>
@ -17,20 +18,17 @@
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.wisdom-framework</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.8.7_1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.44.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@ -40,7 +38,6 @@
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>

View File

@ -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<User> 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);
}
}
}

View File

@ -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<User> getUsers() {
List<User> 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;
}
}

View File

@ -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<User> getUsers();
}

View File

@ -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<User> getUsers() {
// Map<String, User> users = userMapper.getUsers();
// return users.values().toArray(new User[0]);
return userMapper.getUsers();
}
}

View File

@ -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) {

View File

@ -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";
}
}

View File

@ -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<User> getUsers() {
List<User> 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;
}
}

View File

@ -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() + "'" +
"}";
}
}

View File

@ -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("<this package>") 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<User> getUsers();
}

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.repository.UserMapper">
<!-- Get all users -->
<select id="getUsers" resultType="User">
select * from users
</select>
<!-- Add user -->
<insert id="addUser" parameterType="User" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into users (name, password) values (#{name}, #{password})
<!-- NOTE: com.xerial sqlite-jdbc driver does not supporting RETURN_GENERATED_KEYS and useGeneratedKeys will cause error -->
<!-- Uncomment the follow <selectKey> to fix the problem -->
<!-- <selectKey keyProperty="id" order="AFTER" resultType="int">
SELECT last_insert_rowid() as id
</selectKey> -->
</insert>
<!-- Get user by id -->
<select id="getUserById" parameterType="int" resultType="User">
select *
from users
where id = #{id};
</select>
</mapper>

View File

@ -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<User> getUsers() {
return userMapper.getUsers();
}
}

View File

@ -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

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.badbuta.learnspringbootrestfulsqlite.mapper.UserMapper">
<select id="getUsers" resultType="User">
select * from users
</select>
<!-- NOTE: Sqlite JDBC driver not supporting RETURN_GENERATED_KEYS -->
<insert id="insertUser" parameterType="User" keyProperty="id">
<selectKey order="AFTER" keyProperty="id" resultType="int">
SELECT last_insert_rowid()
</selectKey>
insert into users (name,password) values (#{name},#{password})
</insert>
<select id="getUserById" parameterType="int" resultType="User">
select *
from users
where id = #{id};
</select>
<!-- <resultMap id="UserM" type="com.badbuta.learnspringbootrestfulsqlite.entities.User">
<result property="id" column="id" />
<result property="name" column="name" />
<result property="password" column="password" />
</resultMap> -->
</mapper>

View File

@ -5,10 +5,12 @@
<configuration>
<!-- In order to use the application setting here, we need to set the <properties> -->
<properties resource="application.properties"/>
<typeAliases>
<!-- Equivent to application.properties: mybatis.type-aliases-package -->
<package name="com.badbuta.learnspringbootrestfulsqlite.entities"></package>
<package name="com.example.demo.model"></package>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
@ -18,7 +20,10 @@
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapping/UserMapper.xml"/>
</mappers>
<!-- If the *Mapper.xml is placed in the java source folder, we don't need to specify it explicitly -->
<!-- <mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers> -->
</configuration>

View File

@ -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;