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@AllArgsConstructorpublic 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为主键类型:

@Repositorypublic 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@NoArgsConstructorpublic 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@Setterpublic 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@Getterpublic 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两种实现:

  • Github
  • 码云
  • CODE.CHINA

8 参考链接

1、CSDN-JPA之@GeneratedValue注解

2、spring boot jpa学习:2.DAO和Service的自增id、删、查、改操作

如果感觉文章难看,欢送点赞。

同时欢送关注微信公众号:氷泠之路。