반응형

error log

Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: {VO class name} near line 1, column 249 [query문]

 

오류 코드

@Query("SELECT v.studentId AS studentId, v.studentName AS studentName, SUM(v.mathScore) AS mathScore, "
        +"SUM(v.engScore) AS engScore, v.className AS className, v.grade AS grade"
        +"FROM StudentVo v WHERE v.insertDate BETWEEN ?1 AND ?2 GROUP BY v.studentId, v.studentName, v.className, v.grade")

JPA를 사용한 Repository에서 나타난 오류이다

딱봐도 구문이 틀린 오류인데 쿼리문을 봤을때는 이상이 없어서 사실 249번까지 세었는데 로그에서도 틀린 부분이 확인되었다ㅎ

그렇다 FROM 앞에 띄어쓰기가 안되어서 그런거였다

 

수정코드

@Query("SELECT v.studentId AS studentId, v.studentName AS studentName, SUM(v.mathScore) AS mathScore, "
        +"SUM(v.engScore) AS engScore, v.className AS className, v.grade AS grade "
        +"FROM StudentVo v WHERE v.insertDate BETWEEN ?1 AND ?2 GROUP BY v.studentId, v.studentName, v.className, v.grade")
 

 

결론 : 로그를 잘보자

반응형
반응형

데이터베이스 1개 연결

SpringBoot에서 제공하는 형식에 따라 spring 하위에 yaml 파일을 설정하기 때문에 별도 Configuration 설정을 불필요하다.

spring
  datasource:
    driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    url: jdbc:sqlserver://localhost:8080;databaseName={dbName};encrypt=true;trustServerCertificate=true
    username : {ID}
    password: {PW}
  jpa:
    mapping-resources: {native query path}

 

데이터베이스 2개 연결

nativeQuery xml 파일은 spring에 설정해두고 multi라는 property 값을 설정하여 datasource를 나누었다.

위에서 1개 연결 설정시에는 SpringBoot에서 제공하는 형식에 맞추기 때문에 「url(또는 URL) : url 주소」로 설정하지만,

2개의 DB에 연결을 할때는 「jdbc-url : url 주소」 로 설정해주어야 한다고 한다.

이유는 그것이 HikariCP의 Database URL 설정이니까! (끄덕)

그냥 url로 했다가 url 값을 못찾는 오류가 발생했었으니 조심하자.

spring:
  jpa:
    mapping-resources: /query/nativeQuery.xml
multi:
  datasource:
    main:
      driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
      jdbc-url: jdbc:sqlserver://localhost:8080;databaseName={dbName1};encrypt=true;trustServerCertificate=true
      username: {ID}
      password: {PW}
      base-package: {repository path ex)com.beige0905.test.repository}
      jpa:
        database-platform: org.hibernate.dialect.SQLServer2012Dialect
        show-sql: true
        format-sql: true
        use-sql-comments: true
        hibernate.ddl-auto: update
        generate-ddl: false
    second:
      driver-class-name:
      jdbc-url: jdbc:sqlserver://localhost:8080;databaseName={dbName2};encrypt=true;trustServerCertificate=true
      username: {ID}
      password: {PW}
      base-package: {repository path ex)com.beige0905.test.repository}
      jpa:
        database-platform: org.hibernate.dialect.SQLServer2012Dialect
        show-sql: true
        format-sql: true
        use-sql-comments: true
        hibernate.ddl-auto: update
        generate-ddl: false

 

MainDBConfiguration

package com.beige0905.test.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableJpaRepositories(basePackages = {"${multi.datasource.main.base-package}"},
        entityManagerFactoryRef = "EntityManagerFactory",
        transactionManagerRef = "TransactionManager")
public class MPMainDatabaseConfig {

    @Value("${spring.jpa.mapping-resources}")
    String namedQuery;
    @Value("${multi.datasource.main.base-package}")
    String basePackage;
    @Value("${multi.datasource.main.jpa.database-platform}")
    String dbPlatform;

    @Primary
    @Bean(name = "RepositoryDataSource")
    @ConfigurationProperties(prefix = "multi.datasource.main")
    public DataSource getDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean(name = "EntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean masterEntityManager(@Qualifier("RepositoryDataSource") DataSource dataSource) {

        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan(basePackage);
        em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        em.setMappingResources(namedQuery);

        Properties jpaProperties = new Properties();
        jpaProperties.put("hibernate.dialect", dbPlatform);
        em.setJpaProperties(jpaProperties);

        return em;
    }

    @Primary
    @Bean(name = "TransactionManager")
    public PlatformTransactionManager transactionManager(@Qualifier("EntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager(entityManagerFactory.getObject());
        transactionManager.setNestedTransactionAllowed(true);
        return transactionManager;
    }
}

 

second Configuration

main Configuration과 동일하지만  Bean name을 다르게 설정해야하고 @Primary annotation은 제외한다

package com.beige0905.test.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableJpaRepositories(basePackages = {"${multi.datasource.second.base-package}"},
        entityManagerFactoryRef = "SubEntityManagerFactory",
        transactionManagerRef = "SubTransactionManager")
public class MPEntryLogConfig {
    @Value("${multi.datasource.second.base-package}")
    String basePackage;
    @Value("${multi.datasource.second.jpa.database-platform}")
    String dbPlatform;

    @Bean(name = "SubRepositoryDataSource")
    @ConfigurationProperties(prefix="multi.datasource.second")
    public DataSource getDataSource() { return DataSourceBuilder.create().build(); }

    @Bean(name = "SubEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean masterEntityManager(@Qualifier("SubRepositoryDataSource") DataSource dataSource) {

        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan(basePackage);
        em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());

        Properties jpaProperties = new Properties();
        jpaProperties.put("hibernate.dialect", dbPlatform);
        em.setJpaProperties(jpaProperties);

        return em;
    }

    @Bean(name = "SubTransactionManager")
    public PlatformTransactionManager transactionManager(@Qualifier("SubEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager(entityManagerFactory.getObject());
        transactionManager.setNestedTransactionAllowed(true);
        return transactionManager;
    }
}

 

MainVo

package com.beige0905.test.repository.vo;

import lombok.*;

import javax.persistence.*;
import java.sql.Timestamp;

@Getter
@Setter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "product")
public class MainVo {

    public enum STATUS {
        AVAILABLE,
        UNAVAILABLE
    }
    
    @Id
    @Column(name = "product_no")
    private int productNo;
    
    @Column(name = "product_name")
    private String productName;
    
    @UpdateTimestamp
    @Column(name = "update_date")
    private Timestamp updateDate = Timestamp.valueOf(LocalDateTime.now());
    
    @Column(name ="status")
    @Enumerated(EnumType.STRING)
    private STATUS status;
}

 

MainRepository

package com.beige0905.test.repository;

import com.beige0905.test.repository.vo.MainVo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

public interface MainRepository extends JpaRepository<MainVo, String> {

    @Transactional
    @Modifying
    @Query("update MainVo v set v.status = ?1 where v.no = ?2")
    int updateStatus(MainVo.STATUS processing, int no);
}

 

SecondVo

package com.beige0905.test.second.repository.vo;

import lombok.*;

import javax.persistence.*;
import java.sql.Timestamp;

@Getter
@Setter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "user_info")
public class SecondVo {
    @Id
    @Column(name = "user_no")
    private int userNo;
    @Column(name = "user_name")
    private String userName;
    @Column(name = "join_date")
    private Timestamp joinDate;

}

 

SecondRepository

package com.beige0905.test.secondDb.repository;

import com.beige0905.test.secondDb.repository.vo.SecondVo;
import org.springframework.data.jpa.repository.JpaRepository;

public interface SecondRepository extends JpaRepository<SecondVo, String> {
}

 

vo, repository 생성시 Main과 Second 별도 패키지를 사용해야한다.

 

 

참고자료

https://velog.io/@lehdqlsl/SpringBoot-JPA-Multiple-Databases-%EC%84%A4%EC%A0%95

 

SpringBoot JPA Multiple Databases 설정

다중 DB는 Spring boot 처럼 Auto Configuration되지 않음설정파일(application.yml 또는 application.properties) 값을 읽어와서 연동 할 DB 수 만큼 Datasource를 수동 설정해야함설정파일 대신 코드로 직접 설

velog.io

https://wiki.webnori.com/pages/viewpage.action?pageId=9798284 

 

다중DB 환경설정 - WebFrameWork - WebNORI - IT개발문화 연구소

 

wiki.webnori.com

https://jojoldu.tistory.com/296

 

Spring Boot & HikariCP Datasource 연동하기

안녕하세요? 이번 시간엔 Spring Boot & Hikari Datasource 연동하기 예제를 진행해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하

jojoldu.tistory.com

 

반응형
반응형

상태값을 바꿀때 VO에 선언해 놓은 Enum 값을 넣으려고 아래와 같이 VO를 구성했는데 아래와 같은 오류가 발생했다.

Parameter value [SUCCESS] did not match expected type [java.lang.String (n/a)]

 

VO

@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "log_data")
public class LogVo {

    public enum STATUS {
        SUCCESS,
        FAILED
    }

    @Id
    @Column(name = "seq")
    private long Seq;

    @Column(name = "employee_no")
    private String employeeNo;

    @Column(name = "update_date")
    private LocalDateTime updateDate = LocalDateTime.now();

    @Column(name ="status")
    private String status;
}

 

문제의 VO를 확인해보면 우선 타입이 안맞다는 부분은 작성했던 VO의 status는 String 이여서 enum 으로 설정한 STATUS 타입으로 변환하여 해결했다.

 

그런데도 에러가 발생하였는데 DB 테이블 STATUS에 빈값이 있었기 때문이다.

enum으로 선언한 컬럼은 enum 값중 하나가 무조건 들어가 있어야한다고 한다.

초반에 테이블값을 UNKNOWN으로 디폴트값을 설정하고 enum에 UNKNOWN 값을 추가했다.

반응형
반응형

history 함수를 통해 비동기 페이지의 전후 페이지 이동하는 것을 구현하였는데, 좌측의 사이드바 메뉴가 전페이지 메뉴가 클릭되어 있는 UI 오류를 발견했다.

 

사이드바는 a 태그로 구성되어있고 data-url로 url을 가져와서 이동을 하는 형식이였기 때문에

history.state.data 즉, 이동할 페이지의 url값을 가진 a 태그에 css 값을 주기위한 class명(여기서는 'active')을 추가해주기로 했다

 

window.onpopstate = function(event) {
    if(history.state == null){
        location.href = location.href;
    } else {
        $('nav.sidebar .nav-link.active').removeClass('active')
        $('a[data-url$="'+history.state.data+'"]').addClass('active');
        MP.loadContentWrapper(history.state.data);
    }
}

 

history.state에 값이 있으면 즉, 이동할 페이지가 있다면

모든 nav의 sidebar class에 존재하는 active 클래스를 삭제하고

data-url이 hitory.state.data에 들어가있는 즉, 이동할 페이지와 같은 속성을 가진 a 태그에 active class명을 추가한 후

로드하는 메소드에 url을 담아 실행한다는 의미

$('a[data-url$="'+history.state.data+'"]') 이부분이 자꾸 안잡혔는데

초반에 $('a[data-url$="history.state.data"]') 이런식으로 변수를 따로 안잡아 준게 문제였다

$('a[data-url$='+history.state.data+']') 이렇게 쌍땀을 생략하는것도 불가능하니 변수랑 섞어서 사용할때는 항상 주의하기!

반응형
반응형

HTML5부터 추가된 history.pushState를 활용하여 비동기로 페이지 이동을 구현하게 되었다.

 

page가 실행될때 포함되어 있는 js 파일 내의

페이지가 이동될때마다 호출되는 메서드안에 코드 적용함

 

window.MP.loadContentWrapper = function(url) {
	var staticUrl = location.pathname;

    if(history.state != null && url === history.state.data){
    } else {
        window.history.pushState({data : url}, null, staticUrl);
    }
}

window.onpopstate = function(event) {
    if(history.state == null){
    } else {
        MP.loadContentWrapper(history.state.data);
    }
}

 

주소창에는 주소를 노출시키지 않을 것이라서 주소창에 노출시킬 staticUrl 변수 선언하고 pushState 메소드에 넣어둠

 

history.pushState({data : url}, null, staticUrl);

history.pushState(저장할 데이터, ??, 주소창에표시할url주소)

 

그냥 pushState만 사용하였더니 뒤뒤로 가야 바로 직전 페이지가 나오는 문제가 발생

그래서 history.state로 불러와지는 history 최상단 url이 현재 url과 동일할 경우는 pushState를 실행하지 않기로 함

 

onpopstate는 앞뒤버튼에 대한 이벤트이다

원래 history.state 가 null 일때 location.href = location.href; 코드를 적었는데

생각해보니까 null 이면 뒤로 버튼이 활성화 안될거 같다는 생각을 해서 지움

 

 

 

https://goddaehee.tistory.com/240

 

[JavaScript (15)] Javascript Location, History 객체 (hash, host, href, replace 등)

[JavaScript (15)] Javascript Location, History 객체 (hash, host, href, replace 등) 안녕하세요. 갓대희 입니다. 이번 포스팅은 [ 자바스크립트 윈도우 객체 - 로케이션, 히스토리 객체 ] 입니다. : ) ▶ 1. Location 객

goddaehee.tistory.com

 

반응형
반응형

값을 가져온 후 해당 value 값을 가진 radio 버튼을 체크하고 싶었다

 

실패한 코드

$("input:radio[name='radio-category'][value='data[i].code']").prop('checked', true);

성공한 코드

$("input:radio[name='radio-category'][value='"+data[i].code+"']").prop('checked', true);

 

value값 안에 변수 값을 넣을때는 홑쌍땀을 잘 구분해서 넣자^^!

 

 

반응형
반응형

mapper

mapper파일 내용 전체를 감싸줌

<mapper namespace="mapper파일의고유별칭"></mapper>

 

DML문(insert, update, delete)

- parameterType 속성은 전달받을 값이 없으면 생략가능(속성값 공식 사이트 참고, 잘못된 속성이여도 자동수정해주긴함)
- update/delete도 insert와 동일함

- 실행결과가 처리된 행의 개수(int)이기 때문에 resultType 또는 resultMap에 대한 속성 불필요!

<insert id="sql문 식별자ID" parameterType="전달받을자바타입(풀클래스명) 또는 별칭">
    쿼리문
</insert>

 

select문

- parameterType 속성은 전달받을 값이 없으면 생략가능

- 결과값에 대한 타입을 반드시 지정, resultType(자바 자료형) 또는 resultMap(vo클래스 타입)

 

* 기존에 사용하던 ?(위치홀더) 대신 #{필드명 또는 변수명 또는 map의 키값}으로 작성

<select id="sql문 식별자ID" parameterType="전달받을자바타입(풀클래스명) 또는 별칭"
   resultType="조회결과 반환할 자바타입" 또는 resultMap="조회결과를 뽑아서 매핑할 resultMap의 id">
   쿼리문
</select>

 
resultMap

마이바티스의 핵심 기능
ResultSet으로부터 조회된 컬럼값을 하나씩 뽑아 내가 지정한 VO 객체의 각 필드에 담는 역할

ex) 기존 JDBC코드 적용시 member.setUserId(rset.getString("USER_ID"));

<resultMap id="식별자ID" type="조회된 결과를 담아 반환할 VO객체의 타입(풀클래스명) 또는 별칭">
    <result column="조회결과를 뽑을 DB컬럼명" property="결과를 담을 필드명"/>
    <result column="조회결과를 뽑을 DB컬럼명" property="결과를 담을 필드명"/>
    ...
    ...
</resultMap>

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  
  <mapper namespace="memberMapper">

  		<resultMap type="member" id="memberResultSet">
  			<result column="USER_NO" property="userNo"/>
  			<result column="USER_ID" property="userId"/>
  			<result column="USER_PWD" property="userPwd"/>
  			<result column="USER_NAME" property="userName"/>
  			<result column="EMAIL" property="email"/>
  			<result column="BIRTHDAY" property="birthday"/>
  			<result column="GENDER" property="gender"/>
  			<result column="PHONE" property="phone"/>
  			<result column="ADDRESS" property="address"/>
  			<result column="ENROLL_DATE" property="enrollDate"/>
  			<result column="MODIFY_DATE" property="modifyDate"/>
  			<result column="STATUS" property="status"/>
  		</resultMap>
  		
  		<select id="loginMember" resultMap="memberResultSet" parameterType="member">
  			SELECT * 
			FROM MEMBER 
			WHERE USER_ID = #{userId}
			AND USER_PWD = #{userPwd}
			AND STATUS='Y'
  		</select>
  
	  	<insert id="insertMember" parameterType="member">
	  	   INSERT 
	  	   		INTO MEMBER (
	  	   					 USER_NO,
	            	         USER_ID,
	                 	     USER_PWD,
	                    	 USER_NAME,
		                     EMAIL,
		                     BIRTHDAY,
		                     GENDER,
		                     PHONE,
		                     ADDRESS
		                     )
	            VALUES(
	            		SEQ_UNO.NEXTVAL,
	                    #{userId},
	                    #{userPwd},
	                    #{userName},
	                    #{email},
	                    #{birthday},
	                    #{gender},
	                    #{phone},
	                    #{address}
	                    )                                 
	  	</insert>
  
  </mapper>
반응형

+ Recent posts