이 기사는 금융 컨설팅 분야의 대기업을 위해 Ispirer가 수행한 프로젝트를 기반으로 한 Microsoft SQL Server 현대화에 관한 것입니다. 고객은 SQL Server 데이터베이스의 강력한 기능을 활용하여 고객의 재무 정보를 효율적으로 처리, 처리, 액세스 및 감독했습니다. 클라이언트는 시스템 아키텍처와 유지 관리 비용을 최적화하면서 SQL Server를 클라우드로 마이그레이션하는 것을 목표로 했습니다.
Ispirer 팀은 데이터베이스가 OLTP 모드에서 사용되기 때문에 비즈니스 로직을 Java로 이전할 것을 제안했습니다. 우리는 주제를 주의 깊게 분석하고 SQL Server 비즈니스 규칙을 Java로 변환하는 것과 관련된 고객의 프로젝트를 검토할 것입니다. 또한 기업이 SQL 코드를 애플리케이션 계층으로 마이그레이션하기로 선택한 이유를 조사할 것입니다. 여기에는 전체 프로세스에 대한 포괄적인 검토가 포함됩니다.
클라우드 인프라 비용을 최적화하기 위해 클라이언트는 테이블과 데이터를 PostgreSQL로 마이그레이션하기로 결정했습니다. 우리는 데이터베이스 및 애플리케이션 마이그레이션을 전문으로 하기 때문에 고객은 다음 작업을 우리에게 요청했습니다.
프로젝트 규모가 상당하기 때문에 고객사는 자동화 구현을 통해 마이그레이션 비용을 최소화하기 위해 노력했습니다. Ispirer Toolkit의 효율성을 최적화하기 위해 사전에 사용자 정의가 수행되어야 한다고 결정했습니다. 이 접근 방식을 통해 우리는 T-SCL에서 Java로의 전환율이 약 90%인 도구를 클라이언트 팀에 제공할 수 있었습니다.
이제 테이블과 데이터 마이그레이션에 대해 자세히 살펴보겠습니다.
고객이 온프레미스 SQL Server에서 클라우드로 마이그레이션하기로 선택한 이유를 고려해 보겠습니다. 이러한 전환에는 부인할 수 없는 여러 가지 이점이 포함됩니다.
비용 절감. 클라우드에서 SQL Server에서 PostgreSQL로 마이그레이션하는 주요 원동력 중 하나는 비용 절감입니다. 클라이언트는 SQL Server 데이터베이스를 현장에 유지하는 데 비용이 많이 든다고 생각했습니다. 이는 특수 장비, 소프트웨어 라이센스 및 숙련된 데이터베이스 관리자가 필요했기 때문입니다. 오픈 소스 데이터베이스인 PostgreSQL은 비용 효율적인 대안을 제공합니다. 고객은 클라우드를 사용하여 비용을 절약할 수 있습니다. 큰 금액을 선불로 지불하는 대신 사용한 만큼만 비용을 지불하면 됩니다. 또한 운영 비용도 줄일 수 있습니다.
확장성. 클라우드 컴퓨팅은 온프레미스 인프라보다 더 쉽게 확장하여 더 많은 작업량과 더 많은 사용자에게 서비스를 제공할 수 있습니다. 온프레미스 시스템을 비즈니스 요구 사항에 맞게 확장하려면 조직은 기존 IT 환경에서 비즈니스 서비스를 확장하기 위해 물리적 서버, 소프트웨어 라이선스, 스토리지 및 네트워크 장비를 구입하고 설치해야 했습니다. 클라우드에서는 이러한 리소스의 대부분을 몇 번의 클릭만으로 즉시 사용할 수 있으며 필요한 리소스에 따라 자동으로 확장 및 축소할 수 있습니다. 클라우드의 PostgreSQL은 높은 수준의 확장성을 제공하므로 고객은 수요에 따라 리소스를 쉽게 조정할 수 있습니다.
보안. 클라우드 기술을 수용하면 클라우드 공급자가 제공하는 고급 ID 제어, 액세스 관리 및 인증 시스템 덕분에 주목할만한 보안 이점을 얻을 수 있습니다. 클라우드 제공업체는 사내 IT 팀이나 로컬 시스템보다 더 나은 보안 표준을 갖추고 있어 데이터 환경을 더 안전하게 만드는 경우가 많습니다.
클라우드의 강력한 암호화는 데이터 침해 가능성을 줄입니다. 여기에는 계층화된 보안, 우수한 키 관리, 안전한 액세스 제어가 포함되어 있어 기업이 사용자 액세스를 효과적으로 제어하는 데 도움이 됩니다. 또한 클라우드 제공업체는 데이터 보호를 강화하기 위해 익명성, 복제, 암호화 등의 조치를 구현하여 물리적 액세스를 엄격하게 감독합니다. 2025년까지 약 80%의 기업이 물리적 데이터 센터에서 클라우드 서비스로 전환할 것으로 예상됩니다. 이러한 변화는 클라우드가 제공하는 향상된 보안 이점에 의해 주도됩니다.
SQL Server에서 클라우드의 PostgreSQL로 이동하려면 특정 고려 사항을 염두에 두고 신중한 계획과 실행이 필요합니다. 고객의 프로젝트를 기반으로 우리 팀은 SQL Server 현대화를 위한 다음 단계를 강조했습니다.
스키마 및 데이터 변환. 데이터는 PostgreSQL의 요구 사항에 맞게 정확하게 변환되어야 합니다. 여기에는 날짜 형식 및 문자 인코딩과 같은 미묘한 차이를 처리하는 작업이 포함될 수 있습니다.
제약 조건 및 트리거. 두 데이터베이스의 제약 조건과 트리거 사용의 차이점을 이해하는 것이 중요합니다. 그에 따라 필요한 수정이 필요합니다. 또한 트리거 기능을 애플리케이션으로 이동할 수 있습니다. 하지만 이 작업은 결코 간단하지 않기 때문에 장단점을 따져보는 것이 필수적입니다.
성능 최적화. 마이그레이션 프로세스를 최적화하면 가동 중지 시간과 데이터 전송 시간을 최소화할 수 있습니다. 효율적인 마이그레이션을 위해서는 병렬화를 활용하고, 네트워크 대역폭을 최적화하고, 강력한 하드웨어에 투자하는 것이 중요합니다.
데이터 검증 및 테스트. 데이터 무결성과 기능을 보장하려면 마이그레이션된 데이터에 대한 엄격한 검증이 필요합니다. 광범위한 테스트를 통해 애플리케이션이 새로운 PostgreSQL 데이터베이스와 원활하게 작동하는지 확인합니다.
보안 및 권한. PostgreSQL의 보안 설정, 사용자 계정 및 권한은 원래 SQL Server 설정과 일치하도록 구성되어야 원활한 전환이 보장됩니다.
과거에는 고객이 성능을 향상시킬 것이라고 생각하여 비즈니스 논리에 저장 프로시저를 사용했습니다. 그러나 솔직하게 말하자면 SQL 언어는 애플리케이션 계층과 비교할 때 비즈니스 논리를 수용하는 데 최적의 선택이 아닐 수도 있습니다.
실제로 SQL은 데이터베이스의 데이터만 쿼리하거나 수정합니다. SQL 언어는 쿼리에서 필요한 데이터를 정확하게 얻기 위해 복잡한 조인, 필터링 및 정렬을 수행하는 데 능숙하기 때문에 많은 사람들이 우리에게 썩은 토마토를 던질 수 있습니다. 그렇다면 왜 변경하고 비즈니스 로직을 애플리케이션 수준으로 가져오는 걸까요?
질문은 논리적인 것 같습니다. 좀 더 자세히 답변해 보겠습니다. 아래에는 비즈니스 로직을 애플리케이션으로 이전하는 것에 대해 진지하게 생각해야 하는 4가지 주요 이유가 설명되어 있습니다. 비즈니스 로직을 애플리케이션 계층으로 이동하기로 한 클라이언트의 결정은 다음과 같은 이유에 의해 결정되었습니다.
확장성을 위해서는 비즈니스 로직을 애플리케이션 수준에 저장하는 것이 가장 좋습니다. 왜? 일반적으로 데이터베이스 서버 리소스를 확장하는 것보다 애플리케이션 서버 리소스를 확장하는 것이 훨씬 더 쉽기 때문입니다. 이는 거의 보편적으로 인정됩니다. 대부분의 웹 앱의 경우 처리할 트래픽이 많을 때 일반적으로 더 많은 서버를 추가하는 것이 쉽습니다. 그러나 데이터베이스가 증가된 수요를 수용할 수 있도록 확장할 수 없다면 추가 애플리케이션 서버의 가치는 감소합니다. 데이터베이스 서버를 확장하는 것은 단순히 애플리케이션 서버를 추가하는 것보다 훨씬 더 어렵습니다.
데이터베이스에 비즈니스 논리를 저장하면 유지 관리 문제가 발생할 수 있습니다. 저장 프로시저를 수정하면 많은 응용 프로그램이 중단되고 확장성이 제한되며 "DRY(반복하지 마십시오)" 원칙을 따르기가 어려워질 수 있습니다. 100줄을 초과하는 SQL 코드는 복잡성과 문제 해결의 어려움을 야기하는 경우가 많습니다. 비즈니스 로직을 애플리케이션 계층으로 분리하면 새로운 팀 구성원의 진입이 용이해지고 리팩토링을 위한 보다 직관적인 플랫폼이 제공됩니다.
SQL은 시스템의 비즈니스 규칙을 인코딩하는 데 적합하지 않습니다. 이는 유연하지 않으며 적절한 추상화를 생성할 수 없기 때문에 복잡한 모델을 표현하는 데 의존할 수 없습니다. 이 제한은 비즈니스 논리에 사용하지 않는 주요 이유입니다. 도구나 지원에 관한 것이 아니라 SQL이 더 많은 기회를 제공하는 객체 지향 및 기능적 디자인과 달리 단순하고 표현력이 풍부한 도메인 모델을 만들 수 없다는 것입니다.
소프트웨어 개발에서 코드를 재사용하는 것은 기존 코드를 새로운 작업에 적용할 때 시간과 비용을 절약하는 효율적인 방법입니다. 객체지향 프로그래밍(OOP)은 코드 재활용을 용이하게 하는 방법으로 다양한 애플리케이션에 적합합니다. 그러나 데이터베이스에서 일반적으로 사용되는 SQL은 코드 재사용에 있어 제한된 유연성을 제공합니다. 옵션에는 "뷰" 및 "저장 프로시저" 사용이 포함되지만 후자는 풍부한 매개변수로 이어질 수 있습니다. 프로젝트에 적합한 선택을 하려면 각 방법을 철저하게 탐색하는 것이 필수적입니다.
Transact-SQL을 Java로 변환하려면 다양한 필수 고려 사항을 고려해야 합니다. 이 프로세스에는 SQL 데이터 유형을 해당 Java 데이터 유형에 매핑하여 정확한 데이터 표현을 보장하는 작업이 포함됩니다. 또한 SQL 쿼리를 Java 코드로 변환하는 작업도 포함됩니다. 여기서 Java는 JDBC 또는 Hibernate와 같은 라이브러리를 사용하여 데이터베이스 상호 작용을 처리합니다. 게다가 특정 ESQL 기능에는 Java의 직접적인 상응 기능이 부족하여 잠재적으로 자동 변환이 비효율적이라는 인상을 줄 수 있습니다.
프로젝트의 사용자 정의 단계에서 우리는 자동화 속도를 향상시키는 다양한 변환 솔루션을 만들 수 있었습니다. 처음에는 자동화가 불가능하다고 여겨졌던 이러한 솔루션은 궁극적으로 성공적인 것으로 입증되었습니다. 그 중 일부에 대해 자세히 살펴보겠습니다.
SCOPE_IDENTITY()와 함께 INSERT 문을 변환하여 ID 열에 삽입된 마지막 ID 값을 가져옵니다.
원천:
ALTER PROCEDURE example1 AS BEGIN Declare @idBit int Declare @c int Insert Into tab (c) Values (@c) Set @idBit = SCOPE_IDENTITY() End
표적:
@Service public class Example1 implements IExample1 { @Autowired private JdbcTemplate jdbcTemplate; private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(Example1.class); @Override public Integer spExample1() throws SQLException, Exception { Integer mStatus = 0; KeyHolder keyHolder = new GeneratedKeyHolder(); try { Integer idBit = null; Integer c = null; { final Integer tmpC = c; jdbcTemplate.update(connection-> { PreparedStatement ps = connection.prepareStatement("Insert Into tab(c) \r\n" + " Values(?)", new String[] { "" }); ps.setInt( 1, tmpC); return ps; }, keyHolder); } idBit = Tsqlutils.<Integer > strToNum(keyHolder.getKey().toString(), Integer.class); return mStatus; } catch (Exception e) { LOGGER.error(String.valueOf(e)); mStatus = -1; return mStatus; } } }
원천:
ALTER PROCEDURE [returnSeveralResultSet] @p1 int, @p2 varchar(50) AS Begin select cob_ft, lower(buzon) from tab1 where cob_id = @p1 and cob_ft = @p2 select dep_ft, lower(fiton) from tab2 END
표적:
@Service public class Returnseveralresultset implements IReturnseveralresultset { @Autowired private JdbcTemplate jdbcTemplate; private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(Returnseveralresultset.class); private Integer errorCode = 0; private String sqlState = ""; @Override public Map<String, Object> spReturnseveralresultset(Integer p1, String p2) throws Exception { Integer mStatus = 0; Map<String, Object> outData = new HashMap<>(); List<SqlRowSet> outRsList = new ArrayList<>(); SqlRowSet rowSet; try { rowSet = jdbcTemplate.queryForRowSet("select cob_ft, lower(buzon) from tab1 \r\n" + " where cob_id = ? and cob_ft = ?", p1, p2); outRsList.add(rowSet); rowSet = jdbcTemplate.queryForRowSet("select dep_ft, lower(fiton) from tab2"); outRsList.add(rowSet); return outData; } catch (Exception e) { LOGGER.error(String.valueOf(e)); mStatus = -1; return outData; } finally { outData.put("status", mStatus); outData.put("rsList", outRsList); } } }
원천:
create procedure datediffFn as declare @var1 int set @var1 = DATEDIFF(dd, '1999-01-01', '2000-02-01') set @var1 = DATEDIFF(mm, getdate(), '2000-02-01') set @var1 = DATEDIFF(week, '2005-12-31 23:59:59.9999999', '2006-01-01 00:00:00.0000000');
표적:
public Integer spDatedifffn() throws Exception { Integer mStatus = 0; try { Integer var1 = null; var1 = Tsqlutils.datediff("dd", Tsqlutils.toTimestamp("1999-01-01"), Tsqlutils.toTimestamp("2000-02-01")); var1 = Tsqlutils.datediff("mm", new Timestamp(new java.util.Date().getTime()), Tsqlutils.toTimestamp("2000-02-01")); var1 = Tsqlutils.datediff("week", Tsqlutils.toTimestamp("2005-12-31 23:59:59.9999999"), Tsqlutils.toTimestamp("2006-01-01 00:00:00.0000000")); return mStatus; } catch (Exception e) { LOGGER.error(String.valueOf(e)); mStatus = -1; return mStatus; } }
원천:
create PROCEDURE spSendDbmail AS BEGIN EXEC msdb.dbo.sp_send_dbmail @profile_name = 'New DB Ispirer Profile', @recipients = '[email protected]', @body = '<h1>This is actual message embedded in HTML tags</h1>', @subject = 'Automated Success Message' , @file_attachments = 'C:\Temp\Attached.txt', @body_format='HTML', @copy_recipients = '[email protected]', @blind_copy_recipients = '[email protected]'; END
표적:
import java.util.*; import com.ispirer.mssql.mail.MailService; public class Spsenddbmail { private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(Spsenddbmail.class); public Integer spSpsenddbmail() throws Exception { Integer mStatus = 0; try { MailService.send("New DB Ispirer Profile", "Automated Success Message", "<h1>This is actual message embedded in HTML tags</h1>", "[email protected]", "[email protected]", "[email protected]", "C:\\Temp\\Attached.txt", "HTML"); return mStatus; } catch (Exception e) { LOGGER.error(String.valueOf(e)); mStatus = -1; return mStatus; } } }
원천:
create procedure workWithXml AS begin declare @result bit, @myDoc XML, @myStr varchar(1000), @ProdID INT SET @myDoc = '<Root> <ProductDescription ProductID="1" ProductName="Road Bike"> <Features> <Warranty>1 year parts and labor</Warranty> <Maintenance>3 year parts and labor extended maintenance is available</Maintenance> </Features> </ProductDescription> </Root>' SET @result = @myDoc.exist('(/Root/ProductDescription/@ProductID)[1]') SET @myStr = cast(@myDoc.query('/Root/ProductDescription/Features') as varchar(max)) SET @ProdID = @myDoc.value('(/Root/ProductDescription/@ProductID)[1]', 'int' ) end;
표적:
import java.util.*; import com.ispirer.mssql.xml.XMLUtils; public class Workwithxml { private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(Workwithxml.class); public Integer spWorkwithxml() throws Exception { Integer mStatus = 0; try { Boolean result = null; String myDoc = null; String myStr = null; Integer prodID = null; myDoc = "<Root> " + "<ProductDescription ProductID=\"1\" ProductName=\"Road Bike\"> " + "<Features> " + "<Warranty>1 year parts and labor</Warranty> " + "<Maintenance>3 year parts and labor extended maintenance is available</Maintenance> " + "</Features> " + "</ProductDescription> " + " </Root>"; result = XMLUtils.exist(myDoc, "(/Root/ProductDescription/@ProductID)[1]"); myStr = XMLUtils.query(myDoc, "/Root/ProductDescription/Features"); prodID = XMLUtils.<Integer > value(myDoc, "(/Root/ProductDescription/@ProductID)[1]", Integer.class); return mStatus; } catch (Exception e) { LOGGER.error(String.valueOf(e)); mStatus = -1; return mStatus; } } }
사용자 정의 노력 덕분에 우리 팀은 T-SQL에서 Java로의 전환을 자동화하는 다양한 기술을 성공적으로 개발했습니다. 이를 통해 전체 프로젝트의 전체 마이그레이션 시간이 크게 단축되어 수동 마이그레이션에 비해 마이그레이션 속도가 4배 빨라졌습니다. 툴킷 사용자 정의는 전환을 가속화했을 뿐만 아니라 프로젝트의 전반적인 효율성을 향상시켜 사용자 정의 이니셔티브의 귀중한 영향을 보여주었습니다. 예제에 명시된 메소드는 변환 결과와 함께 클라이언트에 제공됩니다.
결론적으로 비즈니스 논리를 Transact-SQL에서 Java로 전환하는 것은 두 언어와 해당 언어의 고유한 기능에 대한 포괄적인 이해가 필요한 다면적인 프로세스입니다.
이 기사에서 우리는 비즈니스 로직을 앱 계층으로 마이그레이션하는 방법을 철저하게 살펴보고 그러한 전환을 계획하는 사람들에게 귀중한 통찰력을 제공했습니다. 우리 고객의 사례는 이러한 현대화 프로젝트를 자동화하여 마이그레이션 중에 시간과 노력을 크게 절약할 수 있음을 입증합니다. 우리 고객의 프로젝트는 자동화의 놀라운 잠재력을 보여주는 강력한 증거입니다. 이는 자동화가 처음에는 그 능력 이상으로 보일 수 있는 프로세스를 성공적으로 합리화할 수 있는 현대화 측면이 있음을 보여줍니다.
궁극적으로 Transact-SQL에서 Java로의 마이그레이션을 수용하는 것은 더 큰 유연성, 플랫폼 간 호환성 및 확장성을 추구하는 조직을 위한 전략적 움직임입니다. 어려움이 따르지만 이식성, 성능 및 최신 소프트웨어 아키텍처에 대한 적응성 측면에서 이점을 통해 이러한 전환은 데이터베이스 시스템 및 애플리케이션을 현대화하려는 사람들에게 가치 있는 노력이 됩니다.