P2. 配置MySQL和用户注册登录模块

news2025/6/5 17:38:49

P2. 配置MySQL和用户注册登录模块

    • 0 概述
    • Tips
    • 1 预备知识
      • 1.1 SpringBoot 常用模块
      • 1.2 pojo层的实现
      • 1.3 mapper层的实现
      • 1.4 controller层调试CRUD
    • 2 Spring Security
      • 2.1 Spring Security 介绍
      • 2.2 Spring Security 对接数据库
      • 2.3 密码的加密
    • 3 Jwt验证
      • 3.1 传统Session验证方式
      • 3.2 Jwt验证方式
      • 3.3 Jwt验证所需的配置
    • 4 用户注册和登录模块的实现
      • 4.1 用户注册和登录 API 的实现
        • 4.1.1 /user/account/login/
        • 4.1.2 /user/account/info/ /user/account/register/
      • 4.2 用户登录模块的前端
        • 4.2.1 登录页面和 Login 函数及全局变量 user 的定义与使用
        • 4.2.2 获取 user 信息和退出功能
      • 4.3 前端页面授权
      • 4.4 登录状态持久化及小问题处理
    • 5 配置MySQL
      • 5.1 MySQL的基本结构
      • 5.2 IDEA中配置MySQL
      • 5.3 Spring使用MySQL需要的依赖
      • 5.4 图形化使用MySQL

0 概述

以用户注册登录模块为例,学习实现一个具体的业务逻辑的流程

  • 明白 SpringBoot 如何和 MySQL 结合,如何调用数据库中的数据。

  • 了解 pojo, mapper, service, controller 各层的功能及其实现,学会自己写一个业务逻辑。

  • 了解 Jwt 验证原理,知道密码加密的实现。

  • 重点掌握 SpringBoot 实现一个业务 API 功能的3个流程service, service.impl, controller

  • 重点掌握前端如何绑定变量,如何定义全局变量 vuex,如何调用全局变量的函数

    也就是要求掌握第 4 小节所有的代码逻辑。

由于配置 MySQL 不涉及逻辑内容,因此放置在本文的最后介绍。


Tips

  • 在写项目的时候要减少搭轮子的时间,去找网上现成的轮子,比如 JwtUtil 这种。
  • 要放行更多接口的时候可以在 config.SecurityConfig 类中添加。
  • 使用 Mybatis-plus 从而避免写 SQL 语句,不用写简单的 CRUD 操作。具体的接口可以查看Mybatis-plus指南。一些简单的使用在1.4 小节中进行了示范 (querywrapper, userMapper.xxx)。
  • 强烈换成阿里源去下依赖,用外网下依赖直接给我整崩溃了,搞了半天重开了个项目,换成阿里源下载依赖才搞好。
  • 实现和重写接口的方法可以通过 alt + insert 快捷键添加。

1 预备知识

1.1 SpringBoot 常用模块

习惯上会在写后端的时候分层(文件夹)去实现,一共分成 4 层: pojo, mapper, service, controller

  • pojo: 将 MySQL 中的表 table 翻译成 Java 中的类 class

  • mapper: 对 class 的CRUD操作转化成 SQL 语句。

    每个 class 需要相应的CRUD操作,操作完成后最终会存到数据库中,因此需要涉及到 SQL 语句。

  • service: 业务通常要处理多张表,因此组合多个 mapper 实现具体业务。

  • controller: 调度 service,接收前端请求和参数,选择将参数传给哪个 service,再把 service 结果返回给前端。

在这里插入图片描述


1.2 pojo层的实现

pojo 是将 MySQL 中的 user 表转化成 User 类,每个类需要自己的属性和方法,属性需要自己对照数据库中的字段定义,可以通过安装的依赖 lombok 自动实现这些机械化的方法,如 get, set, equals, toString 及有参,无参构造函数。(依赖的安装见 5.3 节)

backend.pojo.User

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String username;
    private String password;
}

1.3 mapper层的实现

本来是需要写大量的 SQL 语句,但 MyBatis-plus 已经帮我们实现好了 SQL 语句,只需要通过 extends BaseMapper<class_name> 集成过来就行,因此不需要写任何 SQL 语句了。

注意 mapper 是一个 interface 而不是 class

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

1.4 controller层调试CRUD

实现完 pojo, mapper 后可以在 controller 层中进行一些调试工作。

让我们测试一下上面的 pojo, mapper,学习一下怎么调用 MyBatis-plus 实现的接口。

@RestController 的作用可以理解成把返回值转成 Json 格式再返回给前端。

下面的CRUD操作仅为了演示怎么调试及使用接口,实际实现某个业务功能的时候有另外的写法,而且一般在 service 实现。

之后在 http://localhost:3000/.../ 中就能查看是否正确取得结果。

注意是自己设置的后端端口号,resources/application.propertiesserver.port=3000 设置。

@RestController
public class UserController {

    @Autowired
    UserMapper userMapper;

    @RequestMapping("/user/all/")
    public List<User> getAllUser() {
        return userMapper.selectList(null);
    }
    
    @RequestMapping("/user/{userId}/")
    public User getUser(@PathVariable int userId) {
        return userMapper.selectById(userId);
    }
    
    // 可以通过封装 queryWrapper 实现复杂一点的操作,这边的功能和上一个函数相同
    @RequestMapping("/user/querywrapper/{userId}/")
    public User getQueryUser(@PathVariable int userId) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", userId);
        return userMapper.selectOne(queryWrapper);
    }
    
    // 查询符合多个条件的直接在 queryWrapper 后面加条件即可
    @RequestMapping("/user/querywrapper/{userId}")
    public List<User> getSomeUser(@PathVariable int userId) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.gt("id", 1).le("id", 2);
        return userMapper.selectList(queryWrapper);
    }
    
    @RequestMapping("/user/add/{userId}/{username}/{password}/")
    public String addUser(
            @PathVariable int userId,
            @PathVariable String username,
            @PathVariable String password) {
        User user = new User(userId, username, password);
        userMapper.insert(user);
        return "add success";
    }

    @RequestMapping("/user/delete/{userId}/")
    public String deleteUser(@PathVariable int userId) {
        userMapper.deleteById(userId);
        return "delete success";
    }
}

2 Spring Security

2.1 Spring Security 介绍

某些功能需要用户拥有权限才能访问,因此需要加上授权机制,进行权限判断,安装依赖 spring-boot-starter-securitySpring 实现好的模块集成进来。会发现上面的操作都需要登录之后才能运行,默认用户名为 user,密码在 IDEA 的控制台中输出。


2.2 Spring Security 对接数据库

现在有个问题就是只能用它默认的用户进行登录,我们希望通过数据库里的用户进行登录,因此需要对 Spring Security 进行配置。

实现 service.impl.UserDetailsServiceImpl 类,继承 UserDetailsService 接口,用来接入数据库信息。

重写其 loadUserByUsername 方法,该方法是通过 username 找到对应用户的 username, password (UserDetails 类型)。

service.impl.UserDetailServiceImpl

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        User user = userMapper.selectOne(queryWrapper);

        if (user == null) throw new RuntimeException("用户不存在!");

        return new UserDetailsImpl(user);
    }
}

实现辅助类 UserDetailsImpl,该类实现接口 UserDetails,该接口的所有方法在 Spring Security 中指出。

这下就可以通过数据库中用户来登录,但是要先把用户密码改成 {noop}<password> 形式,告诉数据库当前字段是未加密的明文。


2.3 密码的加密

这里就有一个很有趣的问题,为什么忘记密码之后只能重置密码,不能知道原来的密码是什么呢?

这是由于密码的加密算法是一个单向的加密,反向解码基本不可能。注册存储的是加密后的密码 encoded_pw_save,每次用户登录输入密码 pw,调用 match 方法看 pw, encoded_pw_save 是否匹配,如果匹配则成功登录。当用户忘记密码之后,数据库存储的是加密后的密码,无法解密出原来的密码,因此只能重置。

config.SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

之后可以在 test 中的 BackendApplicationTests 中进行一些测试,首先输出一下 pw 对应的加密后密文(每次加密结果可能不同),再看一下 pw, encoded_pw 能否匹配,结果为 True,之后把数据库中的密码修改成密文格式就能够正常登陆了(加 {noop} 也无法登录,只能密文登录)。

void contextLoads() {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        System.out.println(passwordEncoder.encode("<pw>"));
        System.out.println(passwordEncoder.matches("<pw>", "<encoded_pw>"));
}

之后可以修改 controller 添加用户 addUser 的实现,直接存储加密之后的密码

@RequestMapping("/user/add/{userId}/{username}/{password}/")
public String addUser(
        @PathVariable int userId,
        @PathVariable String username,
        @PathVariable String password) {
    PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    String encodedPassword = passwordEncoder.encode(password);
    User user = new User(userId, username, encodedPassword);
    userMapper.insert(user);
    return "add success";
}

3 Jwt验证

3.1 传统Session验证方式

所有的页面可以简单的分为2种,一种是公开页面,也就是不登录就可以查看的页面(比如 login, register, homepage);一种是授权页面,也就是需要登录才能查看的页面(其实可以细分成有某个页面的权限才能访问等,但我们的项目暂时用不到这么细致)。

那么问题就来了,Server 如何判断某个用户是否能够访问某个授权页面呢?

首先,先介绍一下传统的 Session 验证方式(如下图所示),仅做了解,并不使用这种方式。

在这里插入图片描述


3.2 Jwt验证方式

对于最近的应用都会遇到跨域问题,通常有多个端(Web, App ...),或是多个服务器,这时 Session 方式就难以处理。

每个服务器都要存下用户的 SessionID

因此提出新的验证方式: Jwt验证,这个方式的优点在于: 方便处理跨域问题,不需要在 Server 端存储信息。

在这里插入图片描述


3.3 Jwt验证所需的配置

​ Jwt验证所需配置具体实现代码

  • 安装依赖 jjwt-api, jjwt-impl, jjwt-jackson
  • 实现 utils.JwtUtil 类,作用是创建和解析 jwt_token
  • 实现 config.filter.JwtAuthenticationTokenFilter 类,用来验证 jwt_token,如果验证成功则提取 user 信息到 Spring Security 的上下文中(存储用户的认证信息,全局共享),以便进行后续的权限验证。
  • 配置 config.SecurityConfig 类,放行登录、注册等接口。

4 用户注册和登录模块的实现

4.1 用户注册和登录 API 的实现

首先更改一下用户 user 的属性,id 改成自增,在 pojo 中为 id 添加注解 @TableId(type = IdType.AUTO)

一共需要实现3个 API:

  • /user/account/token/: 验证用户名密码,验证成功后返回 jwt_token
  • /user/account/info/: 根据 jwt_token 返回用户信息;
  • /user/account/register/: 注册账号。

SpringBoot 实现 API 可以分为 service 声明接口,service.impl 实现接口的具体逻辑,controller 接收前端 url


4.1.1 /user/account/login/

service.user.account.loginService

一般习惯上返回一个 Map<String, String> 接收各种信息,key 要和前端对应起来。

public interface LoginService {
    Map<String, String> login(String username, String password);
}

service.impl.user.account.loginServiceImpl

先封装明文的用户名密码,再验证用户是否登录(失败则进行自动处理),之后取出用户 user,最后把 user_id 封装成 jwt_token

记得加上 @Service 的注解。

@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public Map<String, String> login(String username, String password) {
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(username, password);

        // 如果认证失败(例如,用户名或密码不正确),此方法将抛出一个异常
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        
        UserDetailsImpl loginUser = (UserDetailsImpl) authenticate.getPrincipal();
        User user = loginUser.getUser();

        String jwt = JwtUtil.createJWT(user.getId().toString());

        Map<String, String> map = new HashMap<>();
        map.put("error_message", "success");
        map.put("token", jwt);

        return map;
    }
}

controller.user.account.LoginController

密码需要用密文形式传输,而 get 请求是明文传输,因此 用post 请求,同时要记得把 "/user/account/token/"url 放行。

这里传入的参数就是前端传过来的参数,以 map 形式传入,接口传入的参数需要把对应的 key 抽出来再传入。

记得加上 @RestController 注解。

@RestController
public class LoginController {

    @Autowired
    private LoginService loginService;

    @PostMapping("/user/account/token/")
    public Map<String, String> getToken(@RequestParam Map<String, String> map) {
        String username = map.get("username");
        String password = map.get("password");
        return loginService.getToken(username, password);
    }
}

在前端中可以对 /user/account/token/ 进行调试,resp 就是 LoginServiceImpl 中返回的 Map<String, String>,在前端浏览器中的控制台可以查看结果。

  $.ajax({
    url: "http://localhost:3000/user/account/token/",
    type: "post",
    data: {
      username: "user_1",
      password: "p1",
    },
    success(resp) {
      console.log(resp);
    },
    error(resp) {
      console.log(resp);
    }
  });

4.1.2 /user/account/info/ /user/account/register/

另外两个接口 API 的流程就不再赘述了,过程是一样的,只介绍一下具体逻辑 ServiceImpl 的实现,Controller 中的请求为 Get, Post 也要记得区分,修改删除添加一般都是 Post,获取一般是 Get

service.impl.InfoServiceImpl

这边根据 token 取出 user 的代码直接背过(实现是基于之前 3.3 节的配置),之后但凡要取出 user 直接复制这段代码。

@Service
public class InfoServiceImpl implements InfoService {
    @Override
    public Map<String, String> getinfo() {
        UsernamePasswordAuthenticationToken authentication =
        (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        UserDetailsImpl loginUser = (UserDetailsImpl) authentication.getPrincipal();
        User user = loginUser.getUser();
        
        Map<String, String> res = new HashMap<>();
        res.put("error_message", "success");
        res.put("id", user.getId().toString());
        res.put("username", user.getUsername());
        
        return res;
    }
}

前端测试代码,需要在请求报文的表头带上自己的 jwt_tokentoken 根据第一个 api 获取:

这边是需要登录之后得到 jwt_token 才能获取 user,也就是说需要授权,因此需要加上报文头 headersjwt_token 头部的内容在 config.filter.JwtAuthenticationTokenFilter 中定义,以此验证 jwt_token

    $.ajax({
  	  url: "http://localhost:3000/user/account/info/",
      type: "get",
      headers: {
        Authorization: "Bearer " + "<jwt_token>",
      },
      success(resp) {
        console.log(resp);
      },
      error(resp) {
        console.log(resp);
      }
    });

对于 /user/account/register/ 注册的实现,需要判断用户名和密码是否合法,用户名是否已存在,两次密码是否一致等等,最后再把用户添加到数据库,由于密码的加密,数据库的查询和添加等操作在前面已经介绍过了,请大家尝试自行实现以验证是否理解之前内容。


4.2 用户登录模块的前端

4.2.1 登录页面和 Login 函数及全局变量 user 的定义与使用

用户注册和登录的前端页面的实现(html)就不介绍了,自行去 Bootstrap 搜索喜欢的样式改一改就行。

登陆之后需要记录当前用户 user,这里介绍一下全局变量 user 的定义和使用,可以使用 vuex 使用全局变量。

store.index

需要在 index.js 中把 user 导入到 modules

import MoudleUser from './user'

export default createStore({
  /* ... */
  modules: {
    user: MoudleUser,
  }
})

store.user

简单的来说 state 定义 user 的属性,mutations 定义更新 state 属性的函数,actions 定义需要修改 state 属性的相关函数。

import $ from 'jquery'

export default {
    state: {
        id: "",
        username: "",
        photo: "",
        token: "",
        is_login: false,
    },
    getters: {
    },
    mutations: {
        updateUser(state, user) {
            state.username = user.username;
            state.id = user.id;
            state.photo = user.photo;
            state.is_login = user.is_login;
        },
        updateToken(state, token) {
            state.token = token;
        }
    },
    actions: {
        login(context, data) {
            $.ajax({
                url: "http://localhost:3000/user/account/token/",
                type: "post",
                data: {
                    username: data.username,
                    password: data.password,
                },
                success(resp) {
                    if (resp.error_message == "success") {
                        context.commit("updateToken", resp.token);
                        data.success(resp);
                    } else {
                        data.error(resp);
                    }
                },
                error(resp) {
                    data.error(resp);
                }
            });
        }
    },
    modules: {
    }
}

接下来就需要在登录的时候调用 login 函数即可,介绍一下如何调用和传入参数。

views.user.account.UserAccountLoginView

以下仅展示核心部分代码,首先表单提交时要绑定触发 login 函数(并不是 store 中的 login),另外要将变量 ref 和输入框 input 进行绑定,报错信息通过 {{ error_message }} 绑定。

在自定义的表单提交后触发的 login 中要调用 store.useraction 的函数 store.dispatch("<函数名>, data"),这样之后就能获得用户的 jwt_token,在成功登录之后需要跳转到主页面。

修改 ref 变量时要记得通过 .value 修改值。

<form @submit.prevent = "login">
    <div class="mb-3">
      <label for="username" class="form-label">用户名</label>
      <input v-model = "username" type="text" id="username" class="form-control" placeholder="输入用户名">
    </div>
    <div class="mb-3">
      <label for="password" class="form-label">密码</label>
      <input v-model = "password" type="password" id="password" class="form-control" placeholder="...">
    </div>

     <div class="error-message">{{ error_message }}</div>

     <button type="submit" class="btn btn-primary">提交</button>
</form>


<script>
import { useStore } from 'vuex'
import { ref } from 'vue'
import router from '@/router/index'
    
export default {
    setup() {
        const store = useStore();
        let username = ref('');
        let password = ref('');
        let error_message = ref('');

        const login = () => {
            error_message.value = "";
            store.dispatch("login", {
                username: username.value,
                password: password.value,
                success() {
                    router.push({ name: 'home' });
                },
                error() {
                    error_message.value = "用户名或密码错误";
                }
            });
        };

        return {
            username,
            password,
            error_message,
            login
        }
    }
}
</script>

4.2.2 获取 user 信息和退出功能

store.useraction 中实现函数 getinfo,在登录成功之后获取用户信息(在 login 成功后调用 getinfo)。

...resp 作用是解析 key, value 到当前对象。

getinfo(context, data) {
	$.ajax({
		url: "http://localhost:3000/user/account/info/",
		type: "get",
		headers: {
			Authorization: "Bearer " + context.state.token,
		},
		success(resp) {
			if (resp.error_message === "success") {
				context.commit("updateUser", {
					...resp,
					is_login: true,
				});
				data.success(resp);
			} else {
				data.error(resp);
            }
        },
        error(resp) {
            data.error(resp);
        }
	});
}
const login = () => {
	error_message.value = "";
	store.dispatch("login", {
		username: username.value,
		password: password.value,
		success() {
			store.dispatch("getinfo", {
				success() {
					console.log(store.state.user);
					router.push({ name: 'home' });
				},
				error(resp) {
					console.log(resp);
				}
			});
		},
		error() {
			error_message.value = "用户名或密码错误";
		}
	});
}

之后可以更改导航栏信息 NavBar,如果已登录则显示用户名,否则显示登录和注册按钮。

<ul class="navbar-nav" v-if = "$store.state.user.is_login">
  <li class="nav-item dropdown">
    <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" 
       aria-expanded="false">
 	  {{ $store.state.user.username }}
	</a>
      
    <ul class="dropdown-menu">
	  <router-link class="dropdown-item" :to = "{name: 'user_bot_index'}">Bots</router-link>

	  <li><hr class="dropdown-divider"></li>
                      
      <li><a class="dropdown-item" href="#">退出</a></li>
	</ul>
  </li>
</ul>

<ul class="navbar-nav" v-else>
  <li class="nav-item">
	<router-link class = "nav-link" :to = "{name: 'user_account_login'}" role="button">
	  登录
	</router-link>
  </li>

  <li class="nav-item">
    <router-link class = "nav-link" :to = "{name: 'user_account_register'}" role="button">
	  注册
 	</router-link>
  </li>
</ul>        

之后实现一下 logout 退出功能,和之前的流程一样,只需要把所有 state 清空即可,请自行实现,验证之前的内容是否理解掌握。注册页面的实现同样也不介绍了,都是一样的流程,请自行实现。


4.3 前端页面授权

当用户未登录时,如果访问授权页面则重定向到登录页面,强制要求用户登录,在 router 中实现该功能。

beforeEach 是进到每个页面之前要执行的函数。

import store from "../store/index"

const routes = [
  {
    path: '/',
    name: 'home',
    redirect: '/pk/',
    meta: {
      requestAuth: true,
    }
  },
  /* ... */
  {
    path: '/:catchAll(.*)',
    redirect: "/404/"
  },
]

router.beforeEach((to, from, next) => {
  if (to.meta.requestAuth && !store.state.user.is_login) {
    next({ name: "user_account_login" });
  } else {
    next();
  }
});

4.4 登录状态持久化及小问题处理

每次刷新的时候登录状态都会重置为“未登录状态”,这是因为 jwt_token 是存储在内存中,因此要把 token 存储在浏览器的 localstore 硬盘中。

首先修改 store.user 中的登录和退出函数,分别将 jwt_token 在本地保存和删除。

login(context, data) {
	/* ... */
	success(resp) {
		if (resp.error_message === "success") {
			localStorage.setItem("jwt_token", resp.token);
			context.commit("updateToken", resp.token);
			data.success(resp);
			} else {
				data.error(resp);
			}
		},
	});
},
logout(context) {
	localStorage.removeItem("jwt_token");
	context.commit("logout");
}

之后每次进入到登录页面时,先判断本地是否存在 jwt_token,如果存在再判断 token 是否已经过期(调用 state.dispatch("getInfo"),如果成功则说明未过期),如果未过期则跳转到首页,不需要重新登录。

至此,就实现了登录状态的持久化,每次刷新都会跳转到首页,并且保持登录状态。细心的朋友会发现,每次刷新都会闪一下登录页面,视觉效果不太好,因此 user 中再维护一个全局变量 is_pulling_info,通过这个变量设置默认先不让登录页面的组件显示 v-if = "$store.state.user.is_pulling_info",如果未登录再显示。

登录页面添加以下代码:

const jwt_token = localStorage.getItem("jwt_token");
if (jwt_token) {
	store.commit("updateToken", jwt_token);
	store.dispatch("getinfo", {
		success() {
			router.push({ name: "home" });
			store.commit("updatePullingInfo", false);
		},
		error() {
			store.commit("updatePullingInfo", false);
		},
	});
} else {
	store.commit("updatePullingInfo", false);
	console.log(store.state.user.is_pulling_info);
}

5 配置MySQL

5.1 MySQL的基本结构

在这里插入图片描述

MySQL 中包含多个数据库 databases,每个 database 含有多个表 tables (每张表 table 可以理解成 Java 中的 class 类)。

MySQL 级别相关操作:

启动 MySQL (需要管理员权限): net start mysql80

链接 Mysql: mysql -u root -p

以下操作仅做简单介绍,在实际写项目的时候由于是使用 MyBatis-plus,因此不会用到这些 SQL 语句。

database 级别相关操作:

创建数据库 create database kob_db; 删除数据库 drop database kob_db; 进入数据库 use kob_db;

查看 MySQL 中所有数据库 show databases;

table 级别相关操作: (要先进入需要的 database)

创建表 create table user(id int, username varchar(100), password varchar(100));

删除表 drop table user; 查看当前数据库中所有表 show tables;

row 级别相关操作:

增: insert into user values(1, 'yukiii', '123'); 删: delete from user where id = 1;

查: select * from user; select password from user where id = 1;


5.2 IDEA中配置MySQL

IDEA 中配置 MySQL 数据库 kob_db 的步骤如下:

  1. MySQL 中选择要连接的数据库

在这里插入图片描述

  1. 填写用户,密码,选择要连接到 kob_db 数据库,架构选择默认架构

在这里插入图片描述

在这里插入图片描述

  1. 测试连接,成功后则 IDEA 中配置成功

在这里插入图片描述


5.3 Spring使用MySQL需要的依赖

打开Maven仓库地址,在搜索栏搜索要装的依赖,再将依赖复制到 pom.xml<dependencies>

需要安装的依赖: Spring Boot Starter JDBC, Project Lombok, MySQL Connector/J, mybatis-plus-boot-starter, mybatis-plus-generator,依赖解析的会比较久

pom.xml 添加完后,会发现报红,需要在右侧的 Maven 选项中重新加载所有依赖项。

SpringBoot 在访问 MySQL 时也需要用户名和密码,在 resources/application.properties 添加以下代码:

用户名和密码填写自己的 MySQL 用户,url 中换成 MySQL 端口和数据库名。

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/kob_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

5.4 图形化使用MySQL

对某个表中记录 CRUD 操作都在图形化界面中提供,每个操作之后都要记得点击上传,更新到数据库中。

在这里插入图片描述

进行修改的操作,在右侧右键要修改的表可以跳出相应界面。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1686449.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Langchain-Chatchat的markdownHeaderTextSplitter使用

文章目录 背景排查步骤官方issue排查测试正常对话测试官方默认知识库Debug排查vscode配置launch.json命令行自动启动condadebug知识库搜索测试更换ChineseRecursiveTextSplitter分词器 结论 关于markdownHeaderTextSplitter的探索标准的markdown测试集Langchain区分head1和head…

小白跟做江科大32单片机之学习准备

1.安装好51MDK之后&#xff0c;出现不能正常安装支持包的情况 在线安装支持包——>在keil5软件下点击这个&#xff0c;即可进入更新支持包界面 进去之后找这个 国产的可以找和这个 最后有这个就可以了

【人工智能项目】小车障碍物识别与模型训练(完整工程资料源码)

实物演示效果: 一、绪论: 1.1 设计背景 小车障碍物识别与模型训练的设计背景通常涉及以下几个方面: 随着自动驾驶技术的发展,小车(如无人驾驶汽车、机器人等)需要能够在复杂的环境中自主导航。障碍物识别是实现这一目标的关键技术之一,它允许小车检测并避开路上的障碍物…

JavaScript 动态网页实例 —— 表格处理

表格是网页设计中必不可少的内容之一。本章首先介绍HTML中普通表格的组成结构,然后,在此基础上,介绍如何使用JavaScript设置表格的属性。随后,更具体地介绍操作表格元素的一般方法,主要是对表格行、列的动态增删操作。有了这些基础,在本章的最后介绍对表元的操作,即如何…

C语言 | Leetcode C语言题解之第108题将有序数组转换为二叉搜索树

题目&#xff1a; 题解&#xff1a; struct TreeNode* helper(int* nums, int left, int right) {if (left > right) {return NULL;}// 选择任意一个中间位置数字作为根节点int mid (left right rand() % 2) / 2;struct TreeNode* root (struct TreeNode*)malloc(sizeo…

生产制造边角料核算说明及ODOO演示

今天群里有伙伴提到边角料的处理问题&#xff0c;我们梳理了一下&#xff0c;在生产过程中&#xff0c;如果产生了边角料&#xff0c;核算产成品的投料成本时需要考虑边角料的价值&#xff0c;以确保成本核算的准确性。以下是注意的几点&#xff1a; 一、边角料的入账价值 在生…

poi操作word模板,对原有的word修改

/*** 化工园区调查表** param templatePath* param outPath* param parkInterview*/public static String getDocx(String templatePath, String outPath, ParkInterview parkInterview){File file new File(templatePath);File file1 new File(outPath);if(!file1.exists()…

初识C语言——第二十五天

函数的嵌套调用和链式访问 函数不可以嵌套定义&#xff0c;但可以嵌套调用 链式访问&#xff1a;把一个函数的返回值作为另外一个函数的参数 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h>//写一个函数&#xff0c;每调用一次这个函数&#xff0c;就会 将num…

ArcGIS批量更改所有符号的格式

这期谈一下&#xff0c;如何修改所有符号的样式。 比如&#xff0c;我们需要更改下图的面符号位无轮廓的 该如何批量修改的呢&#xff1f; 视频教学吧&#xff1a; ArcGIS批量更改所有符号的格式 ArcGIS全系列实战视频教程——9个单一课程组合系列直播回放-CSDN博客文章浏览阅…

OracleDG原理

一、DataGuard架构介绍 1、基本介绍 在DG环境中&#xff0c;至少会有两个数据库&#xff0c;一个数据库处于Open状态&#xff0c;对外提供服务&#xff0c;这个数据库叫做primary Database。第二个数据库处于恢复状态&#xff0c;叫做Standby Database。运行时Primay Databas…

C# 正则表达式使用小计

此文档用于记录平时使用正则表达式的心得&#xff0c;不定期更新 基础 实例 替换实例一 //这里匹配以 “( 开头,以 )” 结尾的字符串 private static Regex REGEX_ARG_CONTENT new Regex("""(.*?)""");//此方法用于在匹配到的结果前添加字符…

TG5032CGN TCXO 超高稳定10pin端子型适用于汽车动力转向控制器

TG5032CGN TCXO / VC-TCXO是一款应用广泛的晶振&#xff0c;具有超高稳定性&#xff0c;CMOS输出和使用晶体基振的削波正弦波输出形式。且有低相位噪声优势&#xff0c;是温补晶体振荡器(TCXO)和压控晶体振荡器(VCXO)结合的产物&#xff0c;具有TCXO和VCXO的共同优点&#xff0…

微网群如何协调控制?基于目标级联法的微网群多主体分布式优化调度程序代码!

前言 微电网将分布式电源(distributed generation&#xff0c;DG)与负荷组成有机整体&#xff0c;通过控制策略降低了分布式电源直接并网对大电网的影响&#xff0c;是分布式电源友好并网的有效手段。随着微电网的不断发展&#xff0c;局部范围内的多个微电网互相联结形成微网…

学习记录16-反电动势

一、反电动势公式 在负载下反电势和端电压的关系式为&#xff1a;&#x1d448;&#x1d43c;&#x1d445;&#x1d43f;*&#xff08;&#x1d451;&#x1d456; / &#x1d451;&#x1d461;&#xff09;&#x1d438; E为线圈电动势、 &#x1d713; 为磁链、f为频率、N…

大语言模型预训练新前沿:「最佳适配打包」重塑文档处理标准

源自&#xff1a;机器之心 “人工智能技术与咨询” 发布 声明:公众号转载的文章及图片出于非商业性的教育和科研目的供大家参考和探讨&#xff0c;并不意味着支持其观点或证实其内容的真实性。版权归原作者所有&#xff0c;如转载稿涉及版权等问题&#xff0c;请立即联系我们…

颠覆传统编码,零基础也能飞的工具!

YDUIbuilder以其低代码的设计理念&#xff0c;通过简单的拖拽操作&#xff0c;即使是编程新手也能快速构建出专业的用户界面。这不再是一个遥不可及的梦想&#xff0c;而是一个触手可及的现实。 组件化世界&#xff0c;创意无限&#xff1a;构建梦想中的界面 在YDUIbuilder的组…

java项目之人事系统源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的人事系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于vue的人事系统的主要使用者…

软件无线电学习-第二代移动通信系统过程理解

本文知识内容摘自《软件无线电原理和应用》 无线通信领域让大家感受最深的是民用移动通信的快速发展。民用移动通信在短短的二十年时间里已发展了三代&#xff1a;20世纪80年代的模拟体制(TACS/AMPS)为第一代移动通信(简称1G)&#xff1b;20世纪90年代的数字体制(GSMCDMATDMA)…

.NET快速实现网页数据抓取

网页数据抓取需求 本文我们以抓取博客园10天推荐排行榜第一页的文章标题、文章简介和文章地址为示例&#xff0c;并把抓取下来的数据保存到对应的txt文本中。 请求地址&#xff1a;https://www.cnblogs.com/aggsite/topdiggs 创建控制台应用 创建名为DotnetSpiderExercise的控…

呆马科技----构建智能可信的踏勘云平台

近年来&#xff0c;随着信息技术的快速发展&#xff0c;各个行业都在积极探索信息化的路径&#xff0c;以提升工作效率和服务质量。智慧踏勘云平台是基于区块链和大数据技术构建的全流程智慧可信踏勘解决平台。平台集远程视频、数据显示、工作调度、过程记录为一体&#xff0c;…