前編 Part1 リンク
Part2 Part 2(このページ)
後編 Part3 リンク
記事ディレクトリ
3 カテゴリーマネジメント事業開発
内容:
3.1 共通フィールドの自動入力
3.2 新しいカテゴリ(プロセス全体には、ThreadLocal、インターセプター、メタオブジェクト プロセッサなどが含まれており、学ぶ価値があります)
3.3 カテゴリ情報のページネーションクエリ
3.4 カテゴリの削除(削除されたカテゴリに料理が存在するかどうかを考慮する必要があります。mybatis_plus を完全に使用するのではなく、自分でサービスを作成することを学ぶ価値があります。また、焦点を当てる SQL 最適化もあります)
3.5 分類の変更
3.1 共通フィールドの自動入力
3.1.1 問題分析
- これは、パブリック操作の一部を切り取ってメソッドにカプセル化するようなものです。これらのメソッドは、挿入または更新操作が実行されるたびに自動的に実行され、指定されたパブリックフィールドのプロパティ割り当て
- もう 1 つ問題があります。MetaObjectHandler クラスのメソッドで createUser と UpdateUser を設定するときに、セッションから ID を取得する必要がありますが、このクラスはリクエストを取得できません。この関数はどうすればよいでしょうか? -----TreadLocal の使用
3.1.2 mybatis_plus によって提供されるパブリック フィールドは自動的に入力されます
- 簡単に言うと、insert()やupdate()を実行すると、MetaObjectHandlerの実装クラスのメソッドが自動的に実行され、このクラスをメタデータオブジェクトハンドラと呼びます。このクラスには、フロントエンドによって送信されたメタデータ (ID、ユーザー名など) のメタオブジェクトが自動的に含まれます。
- @TableField に注釈を付けます。そのパラメータ INSERT は、挿入時にこのフィールドの値が自動的に入力され、入力される特定の値が上記の 2 つのメソッドで処理されることを意味します。
3.1.2.1 スレッドローカル
- 同じスレッド
- ThreadLocal とは何ですか?またその機能は何ですか?
3.1.3 バックエンドコード分析
- 1. ThreadLocal カプセル化ツール クラスに基づいて、ユーザーは現在のログイン ユーザー ID (セッション内) を保存して取得します。
public class BaseContext {
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setThreadLocal(Long id){
threadLocal.set(id);
}
public static Long getThreadLocal(){
return threadLocal.get();
}
}
- 2. インターセプタに入った後、セッションを threadLocal にバインドし、後で必要になるパブリック フィールドが自動的に入力されたら、updateUser に入力します。
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
log.info("当前路径:{}", uri);
/**
* HandlerMethod=>Controller中标注@RequestMapping的方法
* 需要配置静态资源不拦截时,添加这块逻辑 => 前后端分离项目
*/
// 是我们的conrtoller中的方法就拦截,如果不是的话,放行,给加载静态资源
if (!(handler instanceof HandlerMethod)) {
log.info("是静态资源或非controller中的方法,放行");
return true;
}
//通过session判断是否登入
if (request.getSession().getAttribute(Contants.SESSION_USERID) == null) {
//这里应该跳转到登入页面,,如何做?
//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
log.info("用户未登入,通过输出流方式向客户端页面响应数据,打回登入页面");
response.getWriter().write(JSON.toJSONString(RetObj.error("NOTLOGIN")));//与前端request.js中的代码呼应
return false;
} else {
log.info("用户已经登入,id={}", request.getSession().getAttribute(Contants.SESSION_USERID));
//进入拦截器后,给threadLocal绑定session,让后面需要的公共字段自动填充的时候,填充这个updateUser
//每次http请求,会分配一个新的线程来处理
BaseContext.setThreadLocal((Long) request.getSession().getAttribute(Contants.SESSION_USERID));
log.info("拦截器这里设置了ThreadLocal,值为:{}",(Long) request.getSession().getAttribute(Contants.SESSION_USERID));
log.info("当前线程id={}",Thread.currentThread().getId());
return true;
}
}
- 3. エンティティ クラスに必要な属性に @Table アノテーションを追加し、自動入力戦略を指定します。
/**
* 创建时间,注意类型LocalDateTime
*/
@TableField(fill = FieldFill.INSERT) //执行insert操作的时候,自动填充该字段
private LocalDateTime createTime;
/**
* 更新时间,注意类型LocalDateTime
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 创建人
*/
@TableField(fill= FieldFill.INSERT) //执行insert操作的时候,自动填充该字段
private Long createUser;
/**
* 修改人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)//执行insert和update操作的时候,自动填充该字段
private Long updateUser;
- 4. カスタム メタデータ オブジェクト プロセッサ
import java.time.LocalDateTime;
/**
* 自定义元数据对象处理器
*/
@Slf4j
@Component ///少了这个导致出错,去复习该注解作用,为什么要交给spring去管理
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段[insert]自动填充");
log.info(metaObject.toString());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("createUser",BaseContext.threadLocal.get());
metaObject.setValue("updateUser",BaseContext.threadLocal.get());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段【update】自动填充");
log.info(metaObject.toString());
log.info("当前线程id为:{}",BaseContext.threadLocal.get());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",BaseContext.threadLocal.get());
}
}
- 上記の 4 つの部分を構成すると、対応する機能が実現されますが、重要なのは @TableField とメタデータ オブジェクト プロセッサの MetaObjectHandler 実装クラスの使用に注意することです。
3.2 新しいカテゴリー
3.2.1 全体的な分析
- 全体的なプロセス: クリックで料理を追加するボタンが押されると、http リクエストが開始され、リクエストを処理するために新しいスレッドが割り当てられ、リクエストは対応する URL アドレスにアクセスします。その後、インターセプタを介して、セッションが空でない場合は解放されます。
http://localhost:8080/backend/page/category/add.do
、ThreadLocal の値が設定されLoginInterceptor
、セッション用に保存された値がカスタム メタデータ オブジェクト プロセッサに設定MyMetaObjectHandler
され、対応する時刻とその他のパブリック フィールドが設定されmetaObject.setValue("createUser",BaseContext.threadLocal.get());
、挿入操作が完了します。 - 同様に、問題がある場合は、グローバル例外処理がアクションを実行します。GlobalExceptionHandler クラスは、コントローラー クラスの例外を監視します。エラーが繰り返される場合は、直接プロンプトを表示することができます。また、その他のエラー (オブジェクトへの null の挿入など)空ではない必須フィールド) が直接プロンプト表示されます: 不明な間違い!
023-03-29 21:35:59.481 ERROR 14664 --- [nio-8080-exec-1] c.e.u.r.c.e.GlobalExceptionHandler
: Duplicate entry '二逼菜' for key 'category.idx_category_name'
- フロントエンドとバックエンドは相互に多重化しており、料理分類か定食分類かをテーブル内のフィールドタイプで区別します。
- 新しいカテゴリ テーブルが使用され、フィールドは次のとおりです (パブリック フィールドの処理戦略は以前と同じであることに注意してください)。mybatis-plusX がすべてを自動生成した後、@Mapper アノテーションなどを確認する必要があります。
- 概要に注意してください。フロントエンドもデータを json 形式でバックエンドに送信するため、バックエンド パラメーターを受け取る場所に @RequestBody アノテーションを追加する必要があります。
- 同様に、料理のカテゴリ名フィールドにも一意の制約があり、繰り返し追加すると、自動的にグローバル例外ハンドラーにジャンプし、ポップアップ プロンプトが表示されます。このレビューの前に、詳細な分析があります。
3.2.2 フロントエンドコード分析
- 2 つのボタンをクリックし、クリックして対応する新しいページにジャンプし、渡されたパラメーターの食事またはクラスに注目してください。後で vue がそれを処理します。
<el-button type="primary" class="continue" @click="addClass('class')">+ 新增菜品分类</el-button>
<el-button type="primary" @click="addClass('meal')">+ 新增套餐分类</el-button>
- addClass() メソッドは、クラスの場合、新しい料理カテゴリを追加する小さなページを開くことに対応します。
// 添加
addClass(st) {
if (st == 'class') {
this.classData.title = '新增菜品分类'
this.type = '1'
} else {
this.classData.title = '新增套餐分类'
this.type = '2'
}
this.action = 'add'
this.classData.name = ''
this.classData.sort = ''
this.classData.dialogVisible = true
},
// 关闭弹窗方法, dialogVisible是自定义属性classData中的一个变量
// el-dialog(对话框),visible.sync控制弹框的显示,就设置这个dialogVisible为true或者false
handleClose(st) {
this.classData.dialogVisible = false
},
- ajax経由でバックエンドにデータを送信し、結果を判定し、データを取得して表示する
//数据提交
submitForm(st) {
const classData = this.classData
const valid = (classData.name === 0 ||classData.name) && (classData.sort === 0 || classData.sort)
if (this.action === 'add') {
if (valid) {
const reg = /^\d+$/
if (reg.test(classData.sort)) {
//正则表达式匹配一下输入是不是数字
//这里就对应转到ajax处理
addCategory({
'name': classData.name,'type':this.type, sort: classData.sort}).then(res => {
console.log(res)
if (res.code === 1) {
this.$message.success('分类添加成功!')
if (!st) {
this.classData.dialogVisible = false
} else {
this.classData.name = ''
this.classData.sort = ''
}
this.handleQuery() //回调之前的init函数进行数据显示
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
} else {
//正则表达式判断输入的如果不是数字
this.$message.error('排序只能输入数字类型')
}
} else {
this.$message.error('请输入分类名称或排序')
}
- addCategory() に対応する Ajax リクエスト
// 新增接口
const addCategory = (params) => {
return $axios({
url: '/backend/page/category/add.do',
method: 'post',
data: {
...params }
})
}
3.2.3 バックエンドコード分析
- バックエンド コードは非常に単純で、前に定義したメタデータ オブジェクト プロセッサを使用したパブリック フィールドの自動入力など、前の部分と同様に、Category エンティティ クラスの @TableFile (xxxx) に対応する属性を追加するだけです。自動的に生成
@PostMapping("/backend/page/category/add.do")
public RetObj<String> addController(@RequestBody Category category){
log.info("category = {}",category);
boolean save = categoryService.save(category);
if (save){
return RetObj.success("成功新增分类");
}else {
return RetObj.error("新增分类失败!");
}
}
3.3 分類情報のページネーションクエリ
3.3.1 全体的な分析
- この部分は前の部分と非常によく似ています。以下の各モジュールにはページング クエリが含まれており、主な違いはテーブル間の違いです。
- ページング クエリのコントローラーでは、まずページング コンストラクターが必ず必要で、次に条件付きコンストラクターも必要です。条件付きコンストラクターが必要な理由を考えてください。名前によるファジー クエリなど、前に追加の条件があります。ここには並べ替え条件があることを忘れないでください。並べ替えフィールドは以前追加したときに設定されており、ここではそれが使用されます。
3.3.2 フロントエンド分析
- getCategoryPage() メソッドから、合計 2 つのパラメーター、page と pageSize が渡され、バックエンドがそれに応じて受信して処理することがわかります。
- ページング クエリの場合は、Page オブジェクト内の recode やその他のデータなど、フロントエンドが必要とするデータに注意してください。その後、バックエンド コードを記述するときに、渡される Page タイプのデータに注意する必要があります
RetObj<Page>
。具体的には:Page pageInfo = new Page(page,pageSize);
バックエンドが条件付きクエリを実行しempService.page(pageInfo,lambdaQueryWrapper)
た後:クエリ結果を pageInfo オブジェクトに直接カプセル化します。。バックエンドはこのオブジェクトから変換された json を解析し、内部のレコードやその他の属性値を取得できます。recodes はクエリされる各カテゴリの情報であり、フロントエンド コードが tableData に割り当てられます。カウントは以下の「合計xxxデータ表示」
methods: {
async init () {
//分页查询,告诉后端需要的page、pageSize
await getCategoryPage({
'page': this.page, 'pageSize': this.pageSize}).then(res => {
if (String(res.code) === '1') {
//注意看,既然前端需要拿这种数据,那后端响应回来的对象,也要符合这种要求!
//mybatisPlus中的Page类中就有就有record属性,所以返回的时候:RetObj<Page>
this.tableData = res.data.records
this.counts = Number(res.data.total)
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
},
// 查询列表接口
const getCategoryPage = (params) => {
return $axios({
url: 'backend/page/category/page.do',
method: 'get',
params
})
}
- ページの表示方法は主にvueのelement-uiコンポーネントを利用し、デフォルトの1ページあたりのアイテム数などの情報が決まります。細かい点もいくつかあり、例えばバックエンドから返されるjsonデータは元々ステータスが1と0しかないのですが、ページ上では通常と無効と表示されていますが、これはフロントエンドが処理したためです。
<span>{ { scope.row.type == '1' ? '菜品分类': '套餐分类' }}</span>
- tableData: []、属性に焦点を当てる
new Vue({
el: '#category-app',
data() {
return {
//下面应该是默认值
action: '',
counts: 0,
page: 1,
pageSize: 10,
//页面展示数据tableData,其他地方取这个数据进行了展示
tableData: [],
type :'',
classData: {
'title': '添加菜品分类',
'dialogVisible': false,
'categoryId': '',
'name': '',
sort: ''
}
}
},
computed: {
},
// created -----------vue实例创建之后(钩子函数)
//mounted -----------DOM挂载之后
created() {
this.init() //init方法下面写了
},
mounted() {
},
- 表全体に具体的に示されているように、テンプレート スロット (テンプレート スロット スコープ) は、コンポーネント内のさまざまなコンテンツを動的に表示する Vue.js の方法です。これにより、親コンポーネントが子コンポーネントにコンテンツを渡し、それらのコンテンツを子コンポーネントでレンダリングできるようになります。「slot-scope」属性は、スロットのコンテキスト データを提供するために使用されます。各行のデータ(行)を取得した後、対応する型のフィールドを呼び出すことができます
<!--表格开始,就是一开始进入页面自动查询那个地方的表格-->
<el-table :data="tableData" stripe class="tableBox">
<!--下面这个是表格中的字段-->
<el-table-column prop="name" label="分类名称"/></el-table-column>
<el-table-column prop="type" label="分类类型">
<!--
模板插槽(template slot-scope)是Vue.js中一种用于在组件中动态展示不同内容的方式。
它允许父组件将内容传递给子组件,并在子组件中渲染这些内容。
"slot-scope"属性用于给插槽提供一些上下文数据。
拿到每一行的数据(row),就可以调出对应类型这个字段
-->
<template slot-scope="scope">
<span>{
{ scope.row.type == '1' ? '菜品分类': '套餐分类' }}</span>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="操作时间">
<template slot-scope="scope">
{
{scope.row.updateTime}}
</template>
</el-table-column>
<el-table-column
prop="sort"
label="排序"
/></el-table-column>
<el-table-column label="操作" width="160" align="center">
<template slot-scope="scope">
<!--操作那一栏的按钮,对应的函数editHandle等等-->
<el-button
type="text"
size="small"
class="blueBug"
@click="editHandle(scope.row)"
>
修改
</el-button>
<el-button
type="text"
size="small"
class="delBut non"
@click="deleteHandle(scope.row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
- ページングナビゲーションバー
<!--共xx条,每页xx条,前往第几页的那一块-->
<el-pagination
class="pageList"
:page-sizes="[10, 20, 30, 40]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="counts"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></el-pagination>
3.3.3 バックエンドコード分析
- 前と同じ、MP のページング プラグイン (前に設定したので、ここでそれを使用します)
/**
* MP分页插件的配置
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
- Page クラスの 1 つは、パラメータ構造、現在のページのパラメータ、および現在のページのサイズを持ちます。
public Page(long current, long size) {
this(current, size, 0L);
}
- コントローラー: 例外が発生した場合、グローバル例外によって直接キャッチされ、これらのコントローラーを監視することを自由に記述できます。
@GetMapping("backend/page/category/page.do")
public RetObj<Page> pageController(int page, int pageSize){
log.info("前端数据:page={},pageSize={}",page,pageSize);
log.info("当前线程id={}",Thread.currentThread().getId());
Page<Category> pageInfo = new Page<>(page,pageSize);
LambdaQueryWrapper<Category> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.orderByAsc(Category::getSort);
//Page<Category> page1 = categoryService.page(pageInfo, lambdaQueryWrapper);
categoryService.page(pageInfo, lambdaQueryWrapper);//直接将查询到的结果封装到pageInfo对象中了
return RetObj.success(pageInfo);
}
3.4 カテゴリの削除
3.4.1 全体的な考え方
- カテゴリの削除は他のことを考えずに非常に簡単で、URL、メソッド、パラメータの型をjsonかその他の型かF12に従って確認するだけでコントローラーを書くことができます。
- 完璧な配慮:特定の料理や定食は特定のカテゴリに関連付けられており、削除することはできません。(後から特定の料理を追加する場合は、こちらのカテゴリーにリンクされます)たとえば、温かい料理のカテゴリにゆでた豚肉のスライスがある場合、その温かい料理を直接削除することはできず、ページにプロンプトが表示されます。現在のカテゴリは料理に関連付けられているため、削除できません。
- 具体的なアイデアは、ディッシュ管理およびパッケージ管理テーブルの対応するエンティティ クラスの categoryId 属性を使用することです。この考え方は考えるべきであり、このように処理されます。CategoryService でメソッドを自分で定義します (以前mybatis_plusさんから提供していただきましたが、ここが練習のポイントです!)。
- 現在のカテゴリの下にある料理の数を確認します。アイデアは、CategoryServiceImpl に dishService と setmealService を導入し、dishService を使用して categoryId の下にある料理の数をクエリし、削除するかどうかを決定することです。つまり、他のサービスを導入する必要がある、CategoryService で目的の削除メソッドを構築します。
上記のコードで実際にできることは、SQLの最適化
3.4.2 SQLの最適化
- ある条件に従って、データベースのテーブルから「yes」と「no」をクエリすると、状態は 2 つしかないのに、なぜ SQL を書くときに SELECT count(*) する必要があるのでしょうか。
ビジネス コードでは、レコードの数に関係なく、1 つ以上の条件に基づいてレコードの有無をクエリする必要があります。一般的なSQLとコードの記述は次のとおりです。
SELECT count(*) FROM table WHERE a = 1 AND b = 2
- 対応するJavaコード
int nums = xxDao.countXxxxByXxx(params);
if ( nums > 0 ) {
//当存在时,执行这里的代码
} else {
//当不存在时,执行这里的代码
}
-
カウントが遅い理由を分析します。
InnoDB は同時にサポートするクラスター化インデックスであり、count コマンドの実装でリアルタイムの統計を使用します。利用可能なセカンダリ インデックスがない場合、count を実行すると、MySQL はテーブル データ全体をスキャンします。データ内に大きなフィールドまたは多数のフィールドがある場合、効率は非常に低くなります (各ページには少数のデータ項目しか含めることができません)。 、より多くの物理ページにアクセスする必要があります)。 -
最適化のアイデア: SQL では count が使用されなくなり、代わりに LIMIT 1 が使用されるようになりました。これにより、クエリ時にデータベースが 1 に遭遇すると返され、さらにいくつあるかを検索し続ける必要がなくなります。
-
SQLとJavaのコード
SELECT 1 FROM table WHERE a = 1 AND b = 2 LIMIT 1
*************************************************
Integer exist = xxDao.existXxxxByXxx(params);
if ( exist != NULL ) {
//当存在时,执行这里的代码
} else {
//当不存在时,执行这里的代码
3.4.3 フロントエンド分析
//删除
deleteHandle(id) {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
'confirmButtonText': '确定',
'cancelButtonText': '取消',
'type': 'warning'
}).then(() => {
deleCategory(id).then(res => {
if (res.code === 1) {
this.$message.success('删除成功!')
this.handleQuery()
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
})
},
- 対応する方法
// 删除当前列的接口
const deleCategory = (ids) => {
return $axios({
url: 'backend/page/category/delete.do',
method: 'delete',
params: {
ids }
})
}
- 現在のカテゴリが何かにバインドされていることをバックエンドが検出した後、フロントエンドが対応する情報を要求できるように、プロンプト情報をフロントエンドに返すにはどうすればよいでしょうか?
- 情報キャプチャの例外があるかどうかに関係なく、バックエンドは次のようなオブジェクトをフロントエンドに渡します: (次のコードを分析します)
@DeleteMapping("backend/page/category/delete.do")
public RetObj<String> deleteController(Long ids){
categoryService.removeIfNotBind(ids);
//如果出异常了下面的代码执行不到,在异常处理中:return RetObj.error("分类包含套餐/菜品,无法删除!");
return RetObj.success("分类信息删除成功");
}
- ここで重要なのは、フロントエンドでこのオブジェクトをどのように扱うかです。オブジェクトに
RetObj.success(Data);
オブジェクトの下の data 属性を読み取らせ、エラーの場合は、返されたオブジェクトの msg 属性を読み取ります。!
- ログをヒットしてブラウザの応答を確認します
console.log(res)
console.log(res.msg)
//删除
deleteHandle(id) {
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
'confirmButtonText': '确定',
'cancelButtonText': '取消',
'type': 'warning'
}).then(() => {
deleCategory(id).then(res => {
if (res.code === 1) {
this.$message.success('删除成功!')
this.handleQuery()
} else {
console.log(res)
console.log(res.msg)
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
})
},
上記の情報を分析すると、res はバックエンドによってブラウザーに返される RetObj オブジェクト (json 形式でフロントエンドに渡されます)、res.msg は RetObj オブジェクトの msg 属性値であることがわかります。!表示に使用しますthis.$message.error(res.msg || '操作失败')
。メッセージが渡された場合はメッセージが表示され、そうでない場合は「操作が失敗しました」と表示されます。
res が json 形式なのはなぜですか? ----GlobalExceptionHandlerクラスにアノテーションが追加されているため
@ResponseBody //因为一会儿的写的方法,需要返回一个json数据,所以需要写这个注解(否则返回的就是那个对象!!),RestController直接复合了这个注解
したがってRetObj.error(customException.getMessage());
、その時点で返されるのは json です。完全
3.4.4 バックエンドコードの記述
3.4.4.1 異常処理解析
- まず例外をカスタマイズする方法 (構築方法) を覚えてください。
/**
* Custom adj.定做(制)的; Custom Exception 自定义异常
*/
public class CustomException extends RuntimeException{
public CustomException(String msg){
super(msg);
}
}
- 次に、手動で例外をスローするコードを記述します。
throw new CustomException("该分类已经绑定了套餐,无法删除!");
- グローバル例外ハンドリングでは、例外をキャッチしてその情報を返すため、アノテーション @ExceptionHandler の形式と仮パラメータに注意してください。
@ControllerAdvice(annotations = {
RestController.class, Controller.class})
@ResponseBody //因为一会儿的写的方法,需要返回一个json数据,所以需要写这个注解(否则返回的就是那个对象!!),RestController直接复合了这个注解
@Slf4j
/**
* 全局异常处理器,结合前端了解一下是如何将错误信息传送给前端的(对象返回之后,如何取出信息)
*/
public class GlobalExceptionHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public RetObj<String> exceptionHandler(SQLIntegrityConstraintViolationException exception){
log.error(exception.getMessage());
if (exception.getMessage().contains("Duplicate entry")){
String[] split = exception.getMessage().split(" ");
return RetObj.error("用户名:" + split[2] + "已存在!");
}else {
return RetObj.error("未知错误!");
}
}
/**
* 构成方法重载
* @param customException 注意要传过来的参数
* @return
*/
@ExceptionHandler(CustomException.class)
public RetObj<String> exceptionHandler(CustomException customException){
return RetObj.error(customException.getMessage());
}
4.customException.getMessage()
それはどのように呼ばれますか?
1 つ目: CustomException extends RuntimeException
2 つ目: public class RuntimeException extends Exception {…}
3 つ目: public class Exception extends Throwable {…}
4 つ目: Throwable:
public String getMessage() {
return detailMessage;
}
**************************************************************
注:
/**
* Specific details about the Throwable. For example, for
* {@code FileNotFoundException}, this contains the name of
* the file that could not be found.
*
* @serial
*/
private String detailMessage;
3.4.4.2 ニーズに合わせたサービスの改善
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category>
implements CategoryService {
@Resource
DishService dishService;
@Resource
SetmealService setmealService;
/**
* 分类如果绑定了菜品,不能直接删除套餐,应该给出对应的提示
* (具体做法就是抛出自定义异常,全局捕获,捕获后将返回对象RetObj中包含了对应的提示信息)
* private Long categoryId; 这个字段的使用很重要!
* 想法就是:select count(*) dish where categoryId = xxx 如果有数据,说明该categoryId下关联了count条数据
*
* @param id 这个id对应分类下有没有关联菜品
* @return
*/
@Override
public boolean removeIfNotBind(Long id) {
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
//dishService.getOne()
LambdaQueryWrapper<Dish> lambdaQueryWrapperDish = new LambdaQueryWrapper<>();
LambdaQueryWrapper<Setmeal> lambdaQueryWrapperSetmeal = new LambdaQueryWrapper<>();
lambdaQueryWrapperDish.eq(Dish::getCategoryId, id);
long countDish = dishService.count(lambdaQueryWrapperDish);
lambdaQueryWrapperSetmeal.eq(Setmeal::getCategoryId, id);
long countMeal = setmealService.count(lambdaQueryWrapperSetmeal);
if (countDish != 0){
//已经关联了套餐或者分类,抛出异常
//throw new RuntimeException("分类绑定了菜品或套餐,无法删除!");
//自己定义异常类
throw new CustomException("该分类已经绑定了菜品,无法删除!");
}else if(countMeal != 0){
throw new CustomException("该分类已经绑定了套餐,无法删除!");
}else{
this.removeById(id);
return true;
}
}
}
3.4.4.2 コントローラーを完成させるだけ
@DeleteMapping("backend/page/category/delete.do")
public RetObj<String> deleteController(Long ids){
/*
前端:
Request URL: http://localhost:8080/backend/page/category/delete.do?ids=1641066227887951873
后端上面的形参要和ids这个名字保持一致。则无法删除,对应的id是null
*/
log.info("要删除的id:{}",ids);
//categoryService.removeById(ids);
categoryService.removeIfNotBind(ids);
//如果出异常了下面的代码执行不到,在异常处理中:return RetObj.error("分类包含套餐/菜品,无法删除!");
return RetObj.success("分类信息删除成功");
}
3.5 分類の変更
3.5.1 全体的な考え方
- 関数のこの部分の実装は比較的単純で、主な注意点は次のとおりです: エコー (確認前に従業員情報を変更するのと似ています) とその後の削除。
3.5.2 フロントエンドコード分析
- データベースがクエリされずにバックエンド処理が実行されると、実際にはエコーされた値があることがわかりますが、これはフロントエンドの影響にすぎません。
<el-button
type="text"
size="small"
class="blueBug"
@click="editHandle(scope.row)" 并且把当前这条数据传过去
>
修改
</el-button>
- ボタンのクリックは @click="editHandle(scope.row)" 関数に対応します
//editHandle(dat)方法,有参,以为要edit,需要name、sort、id信息
editHandle(dat) {
this.classData.title = '修改分类' //展示标题
this.action = 'edit' //使用的是同一个窗口,动态的修改标题,这里标识一下
this.classData.name = dat.name //classDate,模型数据
this.classData.sort = dat.sort //为一些模型数据赋值,赋值完毕就会回显了(输入框和模型数据进行了绑定)
this.classData.id = dat.id
this.classData.dialogVisible = true
},
// 关闭弹窗方法, dialogVisible是自定义属性classData中的一个变量
// el-dialog(对话框),visible.sync控制弹框的显示,就设置这个dialogVisible为true或者false
handleClose(st) {
this.classData.dialogVisible = false
},