後端Spring Boot+前端Android交互+MySQL增刪查改(Java+Kotlin實現)

1 前言&概述

這篇文章是基於這篇文章的更新,主要是更新了一些技術棧以及開發工具的版本,還有修復了一些Bug。

本文是SpringBoot+Android+MySQL的增刪查改的簡單實現,用到的技術包括JacksonOkHttpbouncycastleSpring Data JPA

2 環境

  • Android 4.1.2
  • IDEA 2020.3.1
  • Spring Boot 2.4.2
  • MySQL 8.0.23
  • OpenJDK 11

環境準備就略過了,需要的可以參考這裏。

3 後端

3.1 新建項目

在這裏插入圖片描述

依賴:

在這裏插入圖片描述

3.2 項目結構

在這裏插入圖片描述

3.3 實體類

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private String name;
    private String password;
}

基本的Lombok註解+JPA中的兩個註解:

  • @Id:標識主鍵
  • @GeneratedValue:主鍵生成策略,包括四個

主鍵生成策略如下:

  • GenerationType.TABLE:使用一個特定的數據庫表格來保存主鍵,不依賴外部環境和數據庫的具體實現,但是不能充分利用數據庫特性,一般不會優先使用,且一般配合@TableGenerator使用
  • GenerationType.SEQUENCE:一些數據庫不支持主鍵自增(如Oracle),這時就可以使用SEQUENCE,只有部分(Oracle/DB2/PostgreSQL)支持序列對象,一般不用於其他數據庫
  • GenerationType.IDENTITY:一般意義上的主鍵自增長,插入數據時自動給主鍵複製,比如MySQL中的auto_increment
  • GenerationType.AUTO:主鍵生成策略交給持久化引擎,持久化引擎會根據數據庫在以上三種主鍵策略中選擇其中一種,這是JPA默認的主鍵生成策略

3.4 持久層

繼承CrudRepository<T,ID>T爲實體類,ID爲主鍵類型:

@Repository
public interface UserRepository extends CrudRepository<User,Integer> {
    boolean existsByName(String name);
    User findByNameAndPassword(String name,String password);
}

一個需要注意的點是CrudRepository<T,ID>繼承了Repository<T,ID>,而後者有一個叫查詢方法的特性,就是說能根據一些方法中指定的關鍵字去生成對應的SQL,比如第一個方法existsByName,就根據name判斷用戶是否存在,參數爲一個String name,返回boolean,具體的關鍵字以及例子參考如下:

在這裏插入圖片描述

3.5 業務層

@Transactional
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UserService {
    private final UserRepository repository;

    public boolean exists(User user){
        return repository.existsByName(user.getName());
    }

    public User findByNameAndPassword(User user){
        return repository.findByNameAndPassword(user.getName(),user.getPassword());
    }

    public boolean insert(User user){
        repository.save(user);
        return true;
    }

    public boolean update(User user){
        if(repository.findById(user.getId()).isEmpty()){
            return false;
        }
        repository.save(user);
        return true;
    }

    public boolean deleteById(int id){
        if(!repository.existsById(id)){
            return false;
        }
        repository.deleteById(id);
        return true;
    }
}

註解解釋如下:

  • @Transactional
  • @Service:標識爲業務層,實際效果等價於@Component
  • @RequiredArgsConstructorLombok中的一個註解,主要是爲了解決如下的警告:

在這裏插入圖片描述

其他一些根據方法名就知道含義的方法就不解釋了。

3.6 控制層

@RestController
@RequestMapping("/")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UserController {
    private final UserService service;

    @PostMapping("sign/in/up")
    public ResponseBody signInUp(@RequestBody User user) {
        if (service.exists(user)) {
            User u = service.findByNameAndPassword(user);
            return new ResponseBody(u != null ? ResponseCode.SIGN_IN_SUCCESS : ResponseCode.SIGN_IN_FAILED, u != null ? u.getId() : "");
        }
        return new ResponseBody(service.insert(user) ? ResponseCode.SIGN_UP_SUCCESS : ResponseCode.SIGN_UP_FAILED, "");
    }

    @PutMapping("update")
    public ResponseBody update(@RequestBody User user) {
        return new ResponseBody(service.update(user) ? ResponseCode.UPDATE_SUCCESS : ResponseCode.UPDATE_FAILED, "");
    }

    @DeleteMapping("delete")
    public ResponseBody deleteByName(@RequestParam int id) {
        return new ResponseBody(service.deleteById(id) ? ResponseCode.DELETE_SUCCESS : ResponseCode.DELETE_FAILED, "");
    }

    @GetMapping("test")
    public String test() {
        return "test";
    }
}

註解解釋如下:

  • @RestController:等價於@ResponseBody+@Controller@RepsonseBody是直接返回數據的註解(不是默認的視圖名字),而@Controller@Service類似,查看源碼可知道都是@Component的別名
  • @RequestMapping:表示該類中的方法中包含的Mapping都以此值開頭
  • @PostMapping/@PutMapping/@DeleteMapping/@GetMapping:標識處理POST/PUT/Delete/GET請求的路徑,如果類上添加了@RequestMapping,則把路徑拼接在@RequestMapping的後面,比如這裏的@GetMapping("test")相當於/test

3.7 響應體+響應碼

響應體:

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class ResponseBody {
    private int code;
    private Object data;
}

響應碼:

public class ResponseCode {
    public static final int SIGN_IN_SUCCESS = 2000;
    public static final int SIGN_UP_SUCCESS = 2001;
    public static final int UPDATE_SUCCESS = 2002;
    public static final int DELETE_SUCCESS = 2003;

    public static final int SIGN_IN_FAILED = 3000;
    public static final int SIGN_UP_FAILED = 3001;
    public static final int UPDATE_FAILED = 3002;
    public static final int DELETE_FAILED = 3003;
}

3.8 配置文件

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/userinfo

  jpa:
    open-in-view: false
    hibernate:
      ddl-auto: update

數據庫名字以及用戶名密碼請根據自己需要修改,open-in-view這個選項在JPA默認爲true,設置爲false是爲了抑制一個警告,開啓它的含義是在事務外也可以訪問懶加載的數據,這樣可能會引起手動數據源切換失敗的問題,因此設置爲false

ddl-auto: update表示更新數據表,原有數據保留,而且能在沒有創建表的情況下自動創建表。該參數一共有5個設置選項:updatecreatecreate-dropvalidatenone,具體區別可以查看這裏

3.9 測試

運行後可以訪問本地的localhost:8080/test會看到如下頁面:

在這裏插入圖片描述

這樣就沒問題了,剩下的需要配合Android端測試。

4 Android

4.1 新建項目

Android Q+Java

4.2 依賴+權限

依賴:

implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.1'
implementation 'org.bouncycastle:bcprov-jdk15to18:1.68'
implementation "org.projectlombok:lombok:1.18.16"
annotationProcessor 'org.projectlombok:lombok:1.18.16'

權限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--<application>中-->
android:usesCleartextTraffic="true"

開啓viewBinding

buildFeatures{
    viewBinding = true
}

在這裏插入圖片描述

4.3 項目結構

在這裏插入圖片描述

4.4 實體類

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class User {
    private int id;
    private String name;
    private String password;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }
}

4.5 工具類

public class Utils {
    private static final Keccak.Digest512 digest512 = new Keccak.Digest512();

    public static String encrypt(String origin) {
        return new String(Hex.encode(digest512.digest(origin.getBytes(StandardCharsets.UTF_8))));
    }

    public static String getResponseMessage(int code) {
        String message = "";
        switch (code) {
            case ResponseCode.SIGN_IN_SUCCESS:
                message = "登錄成功";
                break;
            case ResponseCode.SIGN_UP_SUCCESS:
                message = "註冊成功";
                break;
            case ResponseCode.SIGN_IN_FAILED:
                message = "用戶名或密碼錯誤";
                break;
            case ResponseCode.SIGN_UP_FAILED:
                message = "註冊失敗";
                break;
            case ResponseCode.DELETE_FAILED:
                message = "刪除失敗";
                break;
            case ResponseCode.DELETE_SUCCESS:
                message = "刪除成功,自動退出";
                break;
            case ResponseCode.UPDATE_SUCCESS:
                message = "更新成功";
                break;
            case ResponseCode.UPDATE_FAILED:
                message = "更新失敗";
                break;
            case ResponseCode.EMPTY_RESPONSE:
                message = "響應體爲空";
                break;
            case ResponseCode.SERVER_ERROR:
                message = "服務器錯誤";
                break;
            case ResponseCode.JSON_SERIALIZATION:
                message = "JSON序列化錯誤";
                break;
            case ResponseCode.EXIT_SUCCESS:
                message = "退出成功";
                break;
            case ResponseCode.REQUEST_FAILED:
                message = "請求發送失敗";
                break;
            case ResponseCode.UNCHANGED_INFORMATION:
                message = "未修改信息";
                break;
        }
        return message;
    }

    public static void showMessage(Context context, Message message) {
        Toast.makeText(context, getResponseMessage(message.what), Toast.LENGTH_SHORT).show();
    }
}

工具類有三個方法,分別是:

  • 加密:將密碼進行SHA3-512加密,加密後的密碼再發送到後端
  • 獲取對應信息:根據Message獲取對應的提示信息
  • 展示信息:利用Toast展示信息

4.6 響應體+響應碼

響應體:

@NoArgsConstructor
@Setter
@Getter
public class RestResponse {
    private int code;
    private Object data;
}

響應碼:

public class ResponseCode {
    public static final int SIGN_IN_SUCCESS = 2000;
    public static final int SIGN_UP_SUCCESS = 2001;
    public static final int UPDATE_SUCCESS = 2002;
    public static final int DELETE_SUCCESS = 2003;

    public static final int SIGN_IN_FAILED = 3000;
    public static final int SIGN_UP_FAILED = 3001;
    public static final int UPDATE_FAILED = 3002;
    public static final int DELETE_FAILED = 3003;

    public static final int EMPTY_RESPONSE = 4000;
    public static final int SERVER_ERROR = 4001;
    public static final int REQUEST_FAILED = 4002;
    public static final int JSON_SERIALIZATION = 4003;
    public static final int EXIT_SUCCESS = 4004;
    public static final int UNCHANGED_INFORMATION = 4005;
}

4.7 請求URL常量

public class NetworkSettings {
    private static final String HOST = "192.168.1.8";
    private static final String PORT = "8080";
    public static final String SIGN_IN_UP = "http://"+ HOST +":"+PORT + "/sign/in/up";
    public static final String UPDATE = "http://"+ HOST +":"+PORT + "/update";
    public static final String DELETE = "http://"+ HOST +":"+PORT + "/delete";
}

4.8 MainActivity

上一部分代碼吧,剩下的大部分類似,看源碼鏈接即可。

public void signInUp(View view) {
    try {
        String name = binding.name.getText().toString();
        //SHA3-512加密
        String password = Utils.encrypt(binding.password.getText().toString());
        //構造OkHttp請求Request
        Request request = new Request.Builder().url(NetworkSettings.SIGN_IN_UP).post(
        	//請求體類型爲application/json;charset=utf-8,利用了Jackson序列化爲JSON
            RequestBody.create(mapper.writeValueAsString(new User(name, password)), mediaType)
        ).build();
        //異步POST操作,傳入一個Callback回調
        client.newCall(request).enqueue(new Callback() {
        	//若失敗
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
            	//請求失敗信息
                message.what = ResponseCode.REQUEST_FAILED;
                //展示對應信息,注意不能直接使用Toast.make(getApplicationContext(),"message",Toast.LENGTH_SHORT).show()
                //因爲不是同一個線程,需要使用Handler提交,也就是post()方法,參數爲一個線程
                handler.post(()->Utils.showMessage(getApplicationContext(),message));
                e.printStackTrace();
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            	//如果成功
                if (response.isSuccessful()) {
                	//獲取請求體
                    ResponseBody body = response.body();
                    //如果響應體不爲空
                    if (body != null) {
                    	//反序列化爲響應體,包含了一個響應碼以及數據字段
                        RestResponse restResponse = mapper.readValue(body.string(), RestResponse.class);
                        //設置Message
                        message.what = restResponse.getCode();
                        //如果登錄成功
                        if(message.what == ResponseCode.SIGN_IN_SUCCESS){
                            handler.post(()->{
                            	//存儲用戶id
                                signInId = (int)restResponse.getData();
                                //更新UI
                                binding.update.setVisibility(View.VISIBLE);
                                binding.delete.setVisibility(View.VISIBLE);
                                binding.signInUp.setText("退出");
                                binding.signInUp.setOnClickListener(v->signOut(false));
                                //保存舊用戶名以及舊密碼在更新的時候使用
                                oldName = binding.name.getText().toString();
                                oldPassword = binding.password.getText().toString();
                            });
                        }
                    } else {
                    	//空響應體
                        message.what = ResponseCode.EMPTY_RESPONSE;
                        Log.e("RESPONSE_BODY_EMPTY", response.message());
                    }
                } else {
                	//服務器錯誤
                    message.what = ResponseCode.SERVER_ERROR;
                    Log.e("SERVER_ERROR", response.message());
                }
                //根據Message提示對應信息
                handler.post(()->Utils.showMessage(getApplicationContext(),message));
            }
        });
    } catch (JsonProcessingException e) {
        message.what = ResponseCode.JSON_SERIALIZATION;
        Utils.showMessage(getApplicationContext(),message);
        e.printStackTrace();
    }
}

這部分是登錄註冊的代碼,還有更新用戶信息以及刪除用戶的代碼,大部分類似。

5 測試

在這裏插入圖片描述

6 注意事項

如果出現了問題某些功能不能正常實現可以參考此處的一些注意事項以及解決方案

7 源碼

提供了Java+Kotlin兩種實現:

8 參考鏈接

1、CSDN-JPA之@GeneratedValue註解 2、spring boot jpa學習:2.DAO和Service的自增id、刪、查、改操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章