乐趣区

关于mysql:后端Spring-Boot前端Android交互MySQL增删查改JavaKotlin实现

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 两种实现:

  • Github
  • 码云
  • CODE.CHINA

8 参考链接

1、CSDN-JPA 之 @GeneratedValue 注解

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

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

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

退出移动版